Arrays & Hashing
Expert Answer & Key Takeaways
Arrays & Hashing: The Storage & Retrieval Duo
1. Space-Time Complexity Overview
| Operation | Array (Unsorted) | Array (Sorted) | Hash Map (Avg) |
|---|---|---|---|
| Access (Index) | |||
| Search (Value) | |||
| Insertion | (at end) | ||
| Deletion |
2. The Core Philosophy: Trade-offs
3. Essential Mental Models
- The Warehouse (Array): Elements are stored in numbered aisles. If you know the aisle number, you find the item instantly (). If you only know the item name, you must walk through every aisle ().
- The Librarian's Index (Hash Map): A card catalog that tells you exactly which aisle and shelf an item is on. Looking up the card is instant, even in a library with millions of books.
4. Key Patterns to Master
- Frequency Snapshot: Use a Map (or
int[26]) to count occurrences. If count becomes , you found a duplicate. - The Signature Key: Grouping items by a shared property. For anagrams, the signature is the sorted string or character counts.
- Prefix Sums: Transform an array into a "Running Total" array. Useful for finding sums of any subarray in time.
- Hole Filling/Cyclic Mapping: Using the array indices themselves as a "Set" when the values are in range . (e.g.,
nums[nums[i]-1] = -abs(...)) - Sliding Window Hashing: Using a rolling hash or frequency map to track properties of a moving window.
Two Sum
nums and an integer target, return indices of the two numbers such that they add up to target. You may assume that each input would have exactly one solution, and you may not use the same element twice. You can return the answer in any order.Visual Representation
nums = [2, 7, 11, 15], target = 9
1. Complement = target - current
2. Scan through array:
- 2: Complement = 9-2 = 7. Not in map. Store {2: 0}
- 7: Complement = 9-7 = 2. FOUND in map at index 0!
Return [0, 1]Examples
Level I: Brute Force
Intuition
Detailed Dry Run
- i=0 (2), j=1 (7): 2+7=9. Found! Return [0, 1]
Level II: Sorting + Two Pointers
Intuition
Detailed Dry Run
- Pairs with indices: [(3,0), (2,1), (4,2)]
- Sort: [(2,1), (3,0), (4,2)]
- L=0 (2), R=2 (4): sum=6. Found indices 1 and 2.
Level III: HashMap (One-Pass)
Intuition
complement needed (target - current). If complement is already in map, we found the pair. Otherwise, store the current number's index in the map.Detailed Dry Run
- n=2, comp=7. Map: {2: 0}
- n=7, comp=2. Map contains 2 (idx 0). return [0, 1]
Valid Anagram
s and t, return true if t is an anagram of s, and false otherwise. An Anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.Visual Representation
s = "anagram", t = "nagaram"
Method: Count each character
a: 3, n: 1, g: 1, r: 1, m: 1 (in s)
a: 3, n: 1, g: 1, r: 1, m: 1 (in t)
Counts Match -> TrueExamples
Level I: Sorting
Intuition
Detailed Dry Run
Level II: HashMap (Character Count)
Intuition
Detailed Dry Run
Level III: Fixed-Size Array (Optimal)
Intuition
Detailed Dry Run
Contains Duplicate
nums, return true if any value appears at least twice in the array, and return false if every element is distinct.Visual Representation
nums = [1, 2, 3, 1]
Using HashSet:
- 1: Not in set. Set={1}
- 2: Not in set. Set={1, 2}
- 3: Not in set. Set={1, 2, 3}
- 1: EXISTS in set! -> Return TrueExamples
Level I: Brute Force
Intuition
Detailed Dry Run
Level II: Sorting
Intuition
Detailed Dry Run
Level III: HashSet (Optimal)
Intuition
Detailed Dry Run
Group Anagrams
strs, group the anagrams together. You can return the answer in any order. An Anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.Visual Representation
Input: ["eat", "tea", "tan", "ate", "nat", "bat"]
1. Sort each string to create a 'key':
"eat" -> "aet"
"tea" -> "aet"
"tan" -> "ant"
...
2. Group by key in a Hash Map:
"aet" -> ["eat", "tea", "ate"]
"ant" -> ["tan", "nat"]
"abt" -> ["bat"]Examples
Level I: Brute Force (Compare All Pairs)
Intuition
used boolean array to keep track of strings that have already been grouped. This is the simplest baseline approach but inefficient for large inputs.Detailed Dry Run
| String A | String B | Sorted(A) | Sorted(B) | Is Anagram? |
|---|---|---|---|---|
| "eat" | "tea" | "aet" | "aet" | Yes |
| "eat" | "tan" | "aet" | "ant" | No |
| "tan" | "nat" | "ant" | "ant" | Yes |
Level II: Better Solution (HashMap with Sorted String Key)
Intuition
Detailed Dry Run
| Input String | Sorted Key | Action |
|---|---|---|
| "eat" | "aet" | Map["aet"] = ["eat"] |
| "tea" | "aet" | Map["aet"] = ["eat", "tea"] |
| "tan" | "ant" | Map["ant"] = ["tan"] |
Level III: Optimal Solution (Frequency Bucket Key)
Intuition
Detailed Dry Run
Bucket Count Key
| Char | Frequency Count Array |
|---|---|
| eat | [1, 0, 0, 0, 1, ..., 1, ...] (a:1, e:1, t:1) |
| tea | [1, 0, 0, 0, 1, ..., 1, ...] (same signature) |
key = "#1#0#0...#1". Anagrams share the same key without ever sorting.Longest Consecutive Sequence
nums, return the length of the longest consecutive elements sequence. You must write an algorithm that runs in time.Visual Representation
nums = [100, 4, 200, 1, 3, 2]
Step 1: Put all in HashSet: {100, 4, 200, 1, 3, 2}
Step 2: Check each number if it's a START (num-1 not in set):
- 100: (99? No) -> Start! Count 101, 102... (Len: 1)
- 4: (3? Yes) -> Not a start.
- 200: (199? No) -> Start! Count 201... (Len: 1)
- 1: (0? No) -> Start! Count 2, 3, 4 (Len: 4) -> MAX!Examples
Level I: Brute Force (Check Each Number)
Intuition
x in the array, we check if it is the start of a sequence by trying to find x+1, x+2, etc., using a linear scan through the array for each step. This leads to a cubic time complexity.Detailed Dry Run
| Num | Sequence | Max Length |
|---|---|---|
| 100 | [100] (101 not found) | 1 |
| 4 | [4] (5 not found) | 1 |
| 1 | [1, 2, 3, 4] | 4 |
Level II: Better Solution (Sorting)
Intuition
Detailed Dry Run
| Sorted Array | Comparison | Action | Streak |
|---|---|---|---|
| [1, 2, 3, 4, 100, 200] | 2 == 1+1 | Increment | 2 |
| [1, 2, 3, 4, 100, 200] | 100 != 4+1 | Reset | 1 |
Level III: Optimal Solution (HashSet)
Intuition
x is the start if x-1 does not exist in the set. Only then we explore the sequence using a while loop, ensuring each element is visited at most twice.Detailed Dry Run
Start-of-Sequence Logic
| Number | x-1 In Set? | Action | Streak |
|---|---|---|---|
| 100 | 99 (No) | Start Streak | 1 |
| 4 | 3 (Yes) | Skip (not start) | - |
| 1 | 0 (No) | Start Streak | 4 (1,2,3,4) |
[1] -> [2] -> [3] -> [4] (Sequence identified from 1)Product of Array Except Self
nums[i] is (product of all elements to the left of i) * (product of all elements to the right of i). We can precalculate these products in one or two passes.Visual Representation
nums = [1, 2, 3, 4]
Prefixes: [1, 1, 1*2, 1*2*3] = [1, 1, 2, 6]
Suffixes: [2*3*4, 3*4, 4, 1] = [24, 12, 4, 1]
Result: [1*24, 1*12, 2*4, 6*1] = [24, 12, 8, 6]Examples
Level I: Brute Force (Nested Loops)
Intuition
i, we iterate through the entire array again using index j. If i != j, we multiply nums[j] to a running product. This is suboptimal due to the nested traversal.Detailed Dry Run
| Index | Elements Multiplied | Result |
|---|---|---|
| 0 | 2 * 3 * 4 | 24 |
| 1 | 1 * 3 * 4 | 12 |
| 2 | 1 * 2 * 4 | 8 |
| 3 | 1 * 2 * 3 | 6 |
Level II: Better Solution (Prefix & Suffix Arrays)
Intuition
i has a product equal to (everything to its left) * (everything to its right). We can precompute two arrays: prefix (cumulative product from start) and suffix (cumulative product from end).Detailed Dry Run
| Index | Prefix (Left) | Suffix (Right) | Left * Right |
|---|---|---|---|
| 0 | 1 | 234 = 24 | 24 |
| 1 | 1 | 3*4 = 12 | 12 |
| 2 | 1*2 = 2 | 4 | 8 |
| 3 | 123 = 6 | 1 | 6 |
Level III: Optimal Solution (Space Optimized)
Intuition
suffix product to multiply with the stored prefix values. This avoids extra arrays.Detailed Dry Run
Optimization Visual
nums = [1, 2, 3, 4]res = [1, 1, 2, 6] (Storing products of everything to the left)| i | res[i] (Prefix) | Suffix Var | res[i] * Suffix (Final) |
|---|---|---|---|
| 3 | 6 | 1 | 6 |
| 2 | 2 | 1*4=4 | 8 |
| 1 | 1 | 4*3=12 | 12 |
| 0 | 1 | 12*2=24 | 24 |
Index: 0 1 2 3
Nums: 1 2 3 4
Prefix: [1, 1, 2, 6]
Suffix: [24, 12, 4, 1]
Result: [24, 12, 8, 6]Top K Frequent Elements
nums and an integer k, return the k most frequent elements. You may return the answer in any order.Visual Representation
nums = [1, 1, 1, 2, 2, 3], k = 2
Frequency Map: {1: 3, 2: 2, 3: 1}
Buckets (Count -> Elements):
1: [3]
2: [2]
3: [1]
Collect from largest count: [1, 2]Examples
Level I: Brute Force (Sorting Map)
Intuition
k elements.Detailed Dry Run
| Element | Frequency | Sorted Rank |
|---|---|---|
| 1 | 3 | 1 |
| 2 | 2 | 2 |
| 3 | 1 | 3 |
Level II: Better Solution (Min-Heap)
Intuition
k. If we find an element with a higher frequency than the heap's smallest (root), we replace it. This ensures that after processing all elements, the heap contains the k most frequent ones.Detailed Dry Run
Heap Evolution (k=2)
| Step | Element (Freq) | Heap State | Action |
|---|---|---|---|
| 1 | 1 (3) | [(1,3)] | Push |
| 2 | 2 (2) | [(2,2), (1,3)] | Push |
| 3 | 3 (1) | [(3,1), (1,3), (2,2)] | Push & Pop Root (3,1) |
Level III: Optimal Solution (Bucket Sort)
Intuition
0 to N (length of array). We can create an array of buckets where buckets[i] contains all numbers that appear i times. By iterating through buckets from N down to 0, we can collect the top k elements in linear time.Detailed Dry Run
Bucket Sort Visual
nums = [1,1,1,2,2,3], k = 2
Freq Map: {1:3, 2:2, 3:1}| Freq (Bucket Index) | Numbers |
|---|---|
| 3 | [1] |
| 2 | [2] |
| 1 | [3] |
- Freq 3: Add
1->res = [1] - Freq 2: Add
2->res = [1, 2] len(res) == k, STOP.
Subarray Sum Equals K
nums[i...j] is PrefixSum[j] - PrefixSum[i-1]. If this difference equals k, then PrefixSum[j] - k = PrefixSum[i-1]. We use a HashMap to count occurrences of PrefixSum[i-1].Visual Representation
nums = [1, 2, 3], k = 3
Subarrays:
[1, 2] -> Sum = 3 (Valid)
[3] -> Sum = 3 (Valid)
Answer: 2Examples
Level I: Brute Force (All Subarrays)
Intuition
i and end indices j. For each pair, we calculate the sum of elements from i to j. If the sum equals k, we increment our count.Detailed Dry Run
| i | j | Subarray | Sum | Count |
|---|---|---|---|---|
| 0 | 0 | [1] | 1 | 0 |
| 0 | 1 | [1, 1] | 2 | 1 (k=2) |
| 1 | 1 | [1] | 1 | 1 |
| 1 | 2 | [1, 1] | 2 | 2 (k=2) |
Level II: Better Solution (Cumulative Sum O(N²))
nums[j] to the current running sum as you expand the subarray.Detailed Dry Run
| i | j | Running Sum | k=2? |
|---|---|---|---|
| 0 | 0 | 1 | No |
| 0 | 1 | 2 | Yes |
| 0 | 2 | 3 | No |
Level III: Optimal Solution (Prefix Sum + HashMap)
Intuition
nums[i...j] is PrefixSum[j] - PrefixSum[i-1]. If this difference equals k, then PrefixSum[j] - k = PrefixSum[i-1]. We use a HashMap to store the frequencies of prefix sums encountered so far. For each new PrefixSum[j], we check how many times PrefixSum[j] - k has appeared before.Detailed Dry Run
Prefix Sum Visual
nums = [1, 2, 3], k = 3
Initial Map: {0: 1} (Subarray with sum 0 starts from beginning)| Index | Num | Current Prefix Sum (S) | Target (S - k) | Found in Map? | New Map State | Count |
|---|---|---|---|---|---|---|
| 0 | 1 | 1 | -2 | No | {0:1, 1:1} | 0 |
| 1 | 2 | 3 | 0 | Yes (freq 1) | {0:1, 1:1, 3:1} | 1 |
| 2 | 3 | 6 | 3 | Yes (freq 1) | {0:1, 1:1, 3:1, 6:1} | 2 |
Find All Duplicates in an Array
Visual Representation
nums = [4, 3, 2, 7, 8, 2, 3, 1]
Index 0 (4): Mark index 3 -> [4, 3, 2, -7, 8, 2, 3, 1]
Index 1 (3): Mark index 2 -> [4, 3, -2, -7, 8, 2, 3, 1]
...
Index 5 (2): Index 1 is already negative (-3)! DUPLICATE FOUND: 2Examples
Level I: Brute Force (Nested Loops)
Intuition
Detailed Dry Run
| i | j | nums[i] | nums[j] | Match? | Result |
|---|---|---|---|---|---|
| 0 | 1 | 4 | 3 | No | [] |
| 2 | 5 | 2 | 2 | Yes | [2] |
| 1 | 6 | 3 | 3 | Yes | [2, 3] |
Level II: HashSet / Frequency Array
Intuition
Detailed Dry Run
| Num | Seen? | Action | Result |
|---|---|---|---|
| 4 | No | Add to Set | [] |
| 3 | No | Add to Set | [] |
| 2 | No | Add to Set | [] |
| 2 | YES | Add to Result | [2] |
Level III: Optimal Solution (In-place Negation)
Intuition
[1, n], we can use the array itself as a hash table. For each number x, we go to the index abs(x)-1. If the value at that index is already negative, we know we've seen x before, so x is a duplicate. Otherwise, we negate the value to 'mark' it as seen.Detailed Dry Run
Marking Process
nums = [4, 3, 2, 7, 8, 2, 3, 1][4, 3, 2, 7, 8, 2, 3, 1] Iter 1: x=4, Mark index 3 (4-1)
^
[4, 3, 2, -7, 8, 2, 3, 1] Iter 5: x=2, index 1 (2-1) is 3 (>0), mark
^
[4, -3, 2, -7, 8, 2, 3, 1] Iter 6: x=2, index 1 is -3 (<0). FOUND DUPLICATE!Set Matrix Zeroes
Visual Representation
Matrix:
[1, 1, 1]
[1, 0, 1]
[1, 1, 1]
Output:
[1, 0, 1]
[0, 0, 0]
[1, 0, 1]Examples
Level I: Brute Force (Extra Matrix)
Intuition
Detailed Dry Run
Matrix Copy Trace
matrix = [[1, 0], [1, 1]]copy = [[1, 0], [1, 1]]- Find 0 at (0, 1)
- In
copy: Set row 0 to 0s, col 1 to 0s. copybecomes[[0, 0], [1, 0]]- Update original
matrixwithcopyvalues.
Level II: Row and Column Auxiliary Arrays
Intuition
row of size m and col of size n. If matrix[i][j] == 0, we set row[i] = true and col[j] = true. Finally, we traverse the matrix again and set matrix[i][j] = 0 if row[i] or col[j] is true.Detailed Dry Run
| Row Flags | Col Flags | i, j | Action |
|---|---|---|---|
| [F, T, F] | [F, T, F] | 1, 1 | matrix[1][1]=0, so row[1]=T, col[1]=T |
| [F, T, F] | [F, T, F] | 0, 1 | row[0]=F, col[1]=T -> matrix[0][1]=0 |
Level III: Optimal Solution (In-place Flags)
Intuition
- Use two variables
row0andcol0to track if the first row and column themselves need to be zeroed. - Iterate through the rest of the matrix, and if
matrix[i][j] == 0, setmatrix[i][0] = 0andmatrix[0][j] = 0. - Finally, zero out cells based on flags, then the first row/column.
Detailed Dry Run
Visual Process
matrix = [[1, 1, 1], [1, 0, 1], [1, 1, 1]]Initial Scan:
Found 0 at (1, 1) -> Mark First Row/Col
[1, 0, 1] (matrix[0][1] = 0)
[0, 0, 1] (matrix[1][0] = 0)
[1, 1, 1]
Final Update (excluding row0/col0):
[1, 0, 1]
[0, 0, 0] (Row 1 was marked)
[1, 0, 1] (Col 1 was marked)Maximum Sum of Two Non-Overlapping Subarrays
L and M that give the maximum sum, we consider two cases: subarray L comes before M, or M comes before L. We use prefix sums to calculate subarray sums in O(1) and track the maximum sum seen so far.Visual Representation
nums = [0, 6, 5, 2, 2, 5, 1, 9, 4], firstLen = 1, secondLen = 2
Possible Selections:
[6] and [1, 9] -> Sum = 6 + 10 = 16
[0] and [9, 4] -> Sum = 0 + 13 = 13
[9] and [6, 5] -> Sum = 9 + 11 = 20 (MAX!)Examples
Level I: Brute Force (Iterate All Subarrays)
Intuition
firstLen. For each position, iterate through all possible start positions for the second subarray of length secondLen. Check if the two subarrays overlap; if not, calculate their sum and update the maximum.Detailed Dry Run
| Sub1 (L=1) | Sub2 (M=2) | Total Sum | Max |
|---|---|---|---|
| [9] at i=7 | [0,6] at j=0 | 9+11=20 | 20 |
| [9] at i=7 | [4] at j=8 | OOB | 20 |
Level II: Better Solution (Prefix Sum + Nested Loops)
Intuition
Detailed Dry Run
| prefix[i+L]-prefix[i] | prefix[j+M]-prefix[j] | Action |
|---|---|---|
| sum(1) = 9 | sum(2) = 11 | total = 20 |
| sum(1) = 4 | sum(2) = 10 | total = 14 |
Level III: Optimal Solution (Prefix Sum + Sliding Window)
Intuition
L seen so far as we iterate through possible positions of a following subarray of length M. We repeat this with L following M to cover both cases. This gives O(N) by visiting each element once.Detailed Dry Run
Sliding Window Max Sum (L=1, M=2)
nums = [0, 6, 5, 2, 2, 5]Pass 1 (L before M):
[0] [6, 5] 2 2 5 -> sumL=0, maxL=0, sumM=11, res=11
0 [6] [5, 2] 2 5 -> sumL=6, maxL=6, sumM=7, res=max(11, 13)=13
0 6 [5] [2, 2] 5 -> sumL=5, maxL=6, sumM=4, res=max(13, 10)=13Merge K Sorted Arrays
Visual Representation
Arrays:
[1, 4, 5]
[1, 3, 4]
[2, 6]
Heap state (Min-Heap of elements from each array):
[1, 1, 2] -> Pop 1
[4, 1, 2] -> Pop 1
[4, 3, 2] -> Pop 2
...
Result: [1, 1, 2, 3, 4, 4, 5, 6]Examples
Level I: Brute Force (Flatten and Sort)
Intuition
Detailed Dry Run
| All Elements | Sorted Result |
|---|---|
| [1,4,5, 1,3,4, 2,6] | [1,1,2,3,4,4,5,6] |
Level II: Better Solution (Divide and Conquer)
Intuition
Detailed Dry Run
| Step | Pairs Merged | Result Arrays |
|---|---|---|
| 1 | [1,4,5] + [1,3,4] | [1,1,3,4,4,5] |
| 1 | [2,6] | [2,6] |
| 2 | [1,1,3,4,4,5] + [2,6] | [1,1,2,3,4,4,5,6] |
Level III: Optimal Solution (Min Heap)
Intuition
Detailed Dry Run
Min-Heap Merge Process
arrays = [[1, 4], [1, 3], [2, 6]]Heap (val, array_idx, element_idx):
1. Push roots: [(1,0,0), (1,1,0), (2,2,0)]
2. Pop (1,0,0) -> Res: [1]. Push (4,0,1). Heap: [(1,1,0), (2,2,0), (4,0,1)]
3. Pop (1,1,0) -> Res: [1, 1]. Push (3,1,1). Heap: [(2,2,0), (3,1,1), (4,0,1)]First Missing Positive
[1, n+1]. We can use the array itself to store presence information by placing each number x at index x-1. This is a constant space alternative to a HashSet.Visual Representation
nums = [3, 4, -1, 1]
Cyclic Sort (Move nums[i] to index nums[i]-1):
1. swap(3, -1) -> [-1, 4, 3, 1]
2. swap(4, 1) -> [-1, 1, 3, 4]
3. swap(-1, 1) -> [1, -1, 3, 4]
Result:
Index 0: 1 (Correct)
Index 1: -1 (Incorrect! Should be 2)
Return: 2Examples
Level I: Brute Force (Sorting)
Intuition
target (starting at 1). If the current number equals target, increment target. If we find a number greater than target or reach the end, target is the answer.Detailed Dry Run
| nums | target | Action |
|---|---|---|
| [3,4,-1,1] | 1 | Sort -> [-1,1,3,4] |
| -1 | 1 | Skip (not positive) |
| 1 | 1 | Match! target -> 2 |
| 3 | 2 | 3 > 2, Stop. |
| Result: 2 | - | - |
Level II: Better Solution (HashSet)
Intuition
N+1 (where N is array size). The first number not present in the set is the smallest missing positive integer.Detailed Dry Run
| Set | Checking | Found? |
|---|---|---|
| {1, 2, 0} | 1 | Yes |
| {1, 2, 0} | 2 | Yes |
| {1, 2, 0} | 3 | No! Result=3 |
Level III: Optimal Solution (Cyclic Sort)
Intuition
[1, N+1], try to place every number x in the range [1, N] at index x-1. For example, 1 should be at index 0, 2 at index 1, etc. After one pass of swapping, the first index i where nums[i] != i+1 gives the missing number.Detailed Dry Run
Cyclic Sort Visualization
nums = [3, 4, -1, 1]1. nums[0]=3: Swap with nums[2] -> [-1, 4, 3, 1]
2. nums[1]=4: Swap with nums[3] -> [-1, 1, 3, 4]
3. nums[1]=1: Swap with nums[0] -> [1, -1, 3, 4]
4. Done! nums[1] is -1, which is not 1+1=2. Answer: 2Insert Delete GetRandom O(1)
Visual Representation
Insert 10: Array=[10], Map={10: 0}
Insert 20: Array=[10, 20], Map={10: 0, 20: 1}
Remove 10:
1. Swap 10 with last (20): Array=[20, 10]
2. Update Map: {20: 0}
3. Pop last: Array=[20]Examples
Level I: Brute Force (ArrayList only)
Intuition
insert, we first check if the element exists by scanning the entire list, which takes O(N). For remove, we search for the element (O(N)) and then shift remaining elements (O(N)). For getRandom, we pick a random index in O(1). This approach fails the O(1) requirement for insert and remove.Detailed Dry Run
| Action | List | Result |
|---|---|---|
| insert(1) | [1] | true |
| insert(1) | [1] | false (contains) |
| getRandom | [1] | 1 |
Level II: Better Solution (HashSet)
Intuition
insert and remove operations in O(1) average time. However, sets do not support efficient random access by index. To implement getRandom, we must either iterate through the set or convert it to an array/list, both of which take O(N) time. This approach optimizes insert/remove but at the cost of a slow getRandom.Detailed Dry Run
| Action | Set | Result |
|---|---|---|
| insert(1) | {1} | true |
| getRandom | list({1}) | 1 (Slow conversion) |
Level III: Optimal Solution (HashMap + Array)
Intuition
value -> index mapping for O(1) lookup. The key trick is in remove: instead of shifting elements (O(N)), we swap the target element with the last element of the list, update its index in the map, and pop the last element in O(1).Detailed Dry Run
O(1) Removal Visualization
List: [10, 20, 30, 40] Map: {10:0, 20:1, 30:2, 40:3}
Remove 20:
1. Find index of 20 (index = 1)
2. Get last element (40)
3. Set List[1] = 40, Update Map {40:1}
4. Pop last element from List, delete 20 from Map
Result: [10, 40, 30] Map: {10:0, 40:1, 30:2}Count of Smaller Numbers After Self
Visual Representation
nums = [5, 2, 6, 1]
Right of 5: [2, 6, 1] -> 2 smaller (2, 1)
Right of 2: [6, 1] -> 1 smaller (1)
Right of 6: [1] -> 1 smaller (1)
Right of 1: [] -> 0 smaller
Result: [2, 1, 1, 0]Examples
Level I: Brute Force (Nested Loops)
Intuition
i, iterate through all elements to its right (index j > i) and count how many are smaller than nums[i]. This is the most straightforward approach but inefficient for large arrays.Detailed Dry Run
- i=0, v=5: [2, 6, 1] -> 2, 1 are smaller. Count=2.
- i=1, v=2: [6, 1] -> 1 is smaller. Count=1.
- i=2, v=6: [1] -> 1 is smaller. Count=1.
- i=3, v=1: [] -> Count=0. Result: [2, 1, 1, 0]
Level II: Better Solution (Binary Indexed Tree)
Intuition
[1, unique_elements].Detailed Dry Run
BIT State Walkthrough
nums = [5, 2, 6, 1] -> Sorted Unique: [1, 2, 5, 6]
Ranks: {1:1, 2:2, 5:3, 6:4}
Backward Traversal:
1. val=1, rank=1: Query sum(0)=0. Update BIT at index 1.
2. val=6, rank=4: Query sum(3)=1. Update BIT at index 4.
3. val=2, rank=2: Query sum(1)=1. Update BIT at index 2.
4. val=5, rank=3: Query sum(2)=2. Update BIT at index 3.
Result: [2, 1, 1, 0]Level III: Optimal Solution (Modified Merge Sort)
Intuition
left and right, if we pick an element from left, any elements already moved from right to the temporary array are smaller than the current element and were originally to its right. We track original indices to update the counts array correctly.Detailed Dry Run
Merge Step Trace
Left: [5, 2] (Indices: [0, 1])
Right: [6, 1] (Indices: [2, 3])
During Merge [5, 2] and [6, 1]:
- 1 from Right is picked: Smaller count for elements in Left increases.
- 2 from Left is picked: Counts[1] += (number of elements already picked from Right).Valid Sudoku
- Each row must contain the digits 1-9 without repetition.
- Each column must contain the digits 1-9 without repetition.
- Each of the nine 3 x 3 sub-boxes of the grid must contain the digits 1-9 without repetition.
Visual Representation
Box Index Formula: (row / 3) * 3 + (col / 3)
Row 0-2, Col 0-2 -> Box 0
Row 0-2, Col 3-5 -> Box 1
Row 3-5, Col 0-2 -> Box 3Examples
Level I: Triple Pass
Intuition
Detailed Dry Run
Level II: Single Pass with HashSets
Intuition
Detailed Dry Run
- Seen in row 4? No.
- Seen in col 4? No.
- Seen in box (4/3)*3 + 4/3 = 4? No. Store it.
Level III: Bitmasking (Optimal Space)
Intuition
Detailed Dry Run
Encode and Decode Strings
Visual Representation
Input: ["hello", "world"]
Encoding: length + '#' + string
"hello" -> "5#hello"
"world" -> "5#world"
Result: "5#hello5#world"
Decoding: Read length, skip '#', read N chars.Examples
Level I: Simple Delimiter (Risk: Collision)
Intuition
Detailed Dry Run
Level II: Length + Separator (Standard)
Intuition
Detailed Dry Run
Level III: Optimal Solution (Base64 Encoding / Escaping)
Intuition
Detailed Dry Run
Brick Wall
Visual Representation
Row 1: [1, 2, 2, 1] -> Edges at: 1, 3, 5
Row 2: [3, 1, 2] -> Edges at: 3, 4
Frequency of Edges:
1: 1
3: 2 (Max! Draw line here)
5: 1
4: 1
Min bricks crossed = Total Rows - Max Edge FrequencyExamples
Level I: Brute Force
Intuition
Detailed Dry Run
Level II: HashMap (Edge Frequency)
Intuition
Detailed Dry Run
Level III: Optimal Solution (Pointer-based Merge)
Intuition
Detailed Dry Run
Isomorphic Strings
s and t are isomorphic if the characters in s can be replaced to get t. All occurrences of a character must be replaced with another character while preserving the order of characters. No two characters may map to the same character, but a character may map to itself.Visual Representation
s = "egg", t = "add"
e -> a
g -> d
Result: "add" (True)
s = "foo", t = "bar"
f -> b
o -> a
o -> r (Conflict! 'o' already mapped to 'a')Examples
Level I: Two HashMaps (Bidirectional)
Intuition
s must map to exactly one character in t, and vice versa. We use two maps to ensure this 1-to-1 relationship.Detailed Dry Run
Level II: Last Seen Index (Space Optimized)
Intuition
Detailed Dry Run
Level III: Optimal Solution (Canonical Form)
Intuition
Detailed Dry Run
Continuous Subarray Sum
nums and an integer k, return true if nums has a continuous subarray of size at least two whose elements sum up to a multiple of k, or false otherwise.Visual Representation
nums = [23, 2, 4, 6, 7], k = 6
Prefix Sum Modulo k:
- 23 % 6 = 5
- (23+2) % 6 = 25 % 6 = 1
- (25+4) % 6 = 29 % 6 = 5 (FOUND EXISTING REMAINDER!)
Subarray from index 1 to 2 has sum (2+4)=6 (multiple of 6).
Size = 2 (Valid)Examples
Level I: Brute Force (All Subarrays)
Intuition
k and length is , return true.Detailed Dry Run
Level II: Better Solution (Prefix Sum O(N²))
Intuition
Detailed Dry Run
Level III: Prefix Sum + Modulo Hashing
Intuition
remainder -> first_seen_index and check if current_index - first_seen_index >= 2.Detailed Dry Run
- rem=5. Map: {0:-1, 5:0}
- rem=(5+2)%6=1. Map: {0:-1, 5:0, 1:1}
- rem=(1+4)%6=5. Key 5 exists at idx 0. Diff 2-0=2. return true.
Course4All Technical Board
Verified ExpertSenior Software Engineers & Algorithmic Experts
Our DSA content is authored and reviewed by engineers from top tech firms to ensure optimal time and space complexity analysis.
Found an issue or have a suggestion?
Help us improve! Report bugs or suggest new features on our Telegram group.