DSA Crash Course
Foundation Data Structures

1.1 Arrays & Strings

Mark when done:

Deep Dive: Why Array Techniques Work

Beginner tip: An array is just a numbered list. arr[0] is the first item, arr[1] is the second, etc. The key superpower of arrays is that jumping to any position is instant (O(1)) because items are stored side by side in memory.

The Two-Sum Insight (HashMap Pattern)

Imagine you have a bag of numbered balls and you need to find two that add up to 9.

Brute force (slow): Pick up each ball and compare it with every other ball. With 1000 balls, that is 500,000 comparisons.

Smart way (HashMap): As you pick up each ball, write its number on a sticky note and pin it to a board. Before picking up the next ball, check the board: "Do I need this number?" This way you look at each ball only once.

Walk through nums = [2, 7, 11, 15], target = 9:

Step 1: Pick up 2. Need 9-2=7. Board is empty.     Pin "2" to board.
Step 2: Pick up 7. Need 9-7=2. Check board: 2 IS there! Done.

Two steps instead of checking all pairs. That is the power of a HashMap.

The Kadane's Insight (Maximum Subarray)

Walk through nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]:

Index:    0    1    2    3    4    5    6    7    8
Value:   -2    1   -3    4   -1    2    1   -5    4
curr:    -2    1   -2    4    3    5    6    1    5
max:     -2    1    1    4    4    5    6    6    6
                         ^--- reset! 4 > (4 + -2) so start fresh here

Key decision at each index: "Is it better to extend my current subarray or start fresh?" If curr + num < num, the accumulated sum is hurting us -- start over.

The Two Pointers Insight (Sorted Arrays)

For 3Sum: sort [-1, 0, 1, 2, -1, -4] -> [-4, -1, -1, 0, 1, 2]

Fix i=1 (val=-1), lo=2, hi=5:
  -1 + -1 + 2 = 0  -> FOUND! Record [-1,-1,2]. Move both pointers.
  -1 + 0 + 1 = 0   -> FOUND! Record [-1,0,1].

Why sorting helps: With sorted data, you know which direction to move. Sum too small? Move left pointer right (bigger number). Sum too big? Move right pointer left (smaller number). This eliminates O(n) possibilities per step instead of checking them all.

The Prefix/Suffix Insight (Product of Array Except Self)

Problem: return an array where out[i] = product of every nums[j] for j != i.

The "obvious" approach is total = product(nums) then out[i] = total / nums[i]. Two reasons we do not do this:

  1. The constraint usually forbids division. That is the whole point of the problem.
  2. Zeros break it. If any nums[i] == 0, total is 0 and we cannot divide back out cleanly. Handling "exactly one zero" vs "two or more zeros" turns into a special-case mess.

The two-pass trick: each out[i] is (product of everything LEFT of i) * (product of everything RIGHT of i). Compute each side separately.

Walk through nums = [1, 2, 3, 4]:

Pass 1 (left -> right): out[i] = product of nums[0..i-1]
  out[0] = 1                     (nothing to the left)
  out[1] = out[0] * nums[0] = 1 * 1 = 1
  out[2] = out[1] * nums[1] = 1 * 2 = 2
  out[3] = out[2] * nums[2] = 2 * 3 = 6
  After pass 1: out = [1, 1, 2, 6]   <-- prefix products

Pass 2 (right -> left): multiply in suffix product on the fly
  suffix = 1
  i=3: out[3] = out[3] * suffix = 6  * 1  =  6   then suffix *= nums[3] -> suffix = 4
  i=2: out[2] = out[2] * suffix = 2  * 4  =  8   then suffix *= nums[2] -> suffix = 12
  i=1: out[1] = out[1] * suffix = 1  * 12 = 12   then suffix *= nums[1] -> suffix = 24
  i=0: out[0] = out[0] * suffix = 1  * 24 = 24   then suffix *= nums[0] -> suffix = 24

Result: out = [24, 12, 8, 6]

Why O(1) extra space (besides the output array): the output itself doubles as the prefix-products buffer in pass 1, then we fold the suffix product into it in pass 2 using a single scalar suffix. No second array needed. Two linear passes, one running variable -- that is the whole trick.


Quick Reference (Cheat Sheet)

Before You Start -- How to Think About Arrays

Why Arrays Exist

Need: Fast access to element by position. How: Contiguous memory -> arr[i] in O(1).

When You See an Array Problem, Ask Yourself

1. Can I SORT it? Sorting unlocks Two Pointers and Binary Search.
2. Can I use EXTRA SPACE (HashMap/Set)? Trading space for time is often the key.
3. Is there a SUBARRAY involved? Think Sliding Window or Prefix Sum.
4. Am I comparing PAIRS? Think Two Pointers (if sorted) or HashMap (if unsorted).
5. Can I process LEFT-TO-RIGHT and build a running answer? Think Kadane's / Greedy.

The 5 Array Patterns to Internalize

PatternSignalCore Idea
HashMap/Set"Find pair", "check existence"O(1) lookup trades space for time
Two PointersSorted input, find pair/tripletConverge from both ends
Prefix/Suffix"Product except self", "range sum"Precompute cumulative values
Kadane's"Maximum subarray"Running max, reset when negative
Index as Hash"First missing positive", range [1,n]Use array indices as storage

String-Specific Thinking

  • Strings are just character arrays. Most array techniques apply.
  • Immutability trap: In Python/Java, string concatenation in a loop is O(n^2). Use lists/StringBuilder.
  • Palindrome thinking: Always consider expanding from center outward. Cheaper than checking all substrings.

Common Mistakes

  • Forgetting that Two Pointers requires sorted input (or you sort first)
  • Off-by-one errors in prefix sum: sum(i..j) = prefix[j+1] - prefix[i]
  • Not handling empty arrays / single-element arrays

Problems

#ProblemDifficultyPatternStatus
1Two SumEasyHashMap[ ]
2Best Time to Buy and Sell StockEasyKadane's[ ]
3Contains DuplicateEasyHashSet[ ]
4Product of Array Except SelfMediumPrefix/Suffix[ ]
5Maximum SubarrayMediumDP / Greedy[ ]
6Maximum Product SubarrayMediumTrack min & max[ ]
73SumMediumTwo Pointers[ ]
8Container With Most WaterMediumTwo Pointers[ ]
9Trapping Rain WaterHardStack / Two Ptrs[ ]
10First Missing PositiveHardIndex as hash[ ]
11Longest Palindromic SubstringMediumExpand center[ ]
12Palindromic SubstringsMediumExpand center[ ]

Notes