Master this topic with zero to advance depth.
Arrays and Hashing are the foundational building blocks of efficient data structures. Arrays provide O(1) indexing by memory address, while Hashing (via HashMaps and HashSets) provides average O(1) time for search, insertion, and deletion by mapping keys to indices.
| Operation | Array (Unsorted) | Array (Sorted) | Hash Map (Avg) |
|---|---|---|---|
| Access (Index) | |||
| Search (Value) | |||
| Insertion | (at end) | ||
| Deletion |
Most 'Arrays & Hashing' problems involve trading space for time. By storing information in a Hash Set or Map (extra space), we avoid nested loops and repetitive scans (saving time).
int[26]) to count occurrences. If count becomes , you found a duplicate.nums[nums[i]-1] = -abs(...))Two Sum
Given an array of integers 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.
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
Because nums[0] + nums[1] == 9, we return [0, 1].
Intuition
Try every possible pair of numbers in the array. This involves nested loops where the outer loop picks the first number and the inner loop looks for the second number that sums to the target.
Detailed Dry Run
nums = [2, 7, 11], target = 9
Intuition
If the array is sorted, we can use two pointers (left and right). If their sum is smaller than target, move left pointer right; if larger, move right pointer left. Note: Since we need indices, we must keep track of original positions.
Detailed Dry Run
nums = [3, 2, 4], target = 6
Intuition
Trade space for time. As we iterate, we calculate the 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
nums = [2, 7, 11], target = 9
Valid Anagram
Given two strings 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.
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
Intuition
Anagrams have the same characters in the same frequency. If we sort both strings, they should be identical.
Detailed Dry Run
s="abc", t="cba" s.sort() -> "abc" t.sort() -> "abc" "abc" == "abc" -> True
Intuition
Count character frequencies using a HashMap. This handles Unicode characters more naturally than a fixed-size array.
Detailed Dry Run
s="aa", t="a" Map {a: 2} from s Map {a: 2-1=1} from t Length mismatch detected early -> return False
Intuition
Since we only have 26 lowercase letters, an array of size 26 is more efficient than a HashMap.
Detailed Dry Run
arr = [0]*26 s="a": arr[0]++ -> [1,0...] t="a": arr[0]-- -> [0,0...] All indices zero -> True
Contains Duplicate
Given an integer array nums, return true if any value appears at least twice in the array, and return false if every element is distinct.
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
Intuition
Compare every pair of elements. If any pair is identical, return true.
Detailed Dry Run
[1, 2, 1] (1, 2): No (1, 1): MATCH! True
Intuition
If duplicates exist, they will be adjacent when sorted.
Detailed Dry Run
[1, 2, 3, 1] -> Sort -> [1, 1, 2, 3] nums[0] == nums[1] (1 == 1) -> True
Intuition
Checking membership in a HashSet takes O(1) average time. If an element is already in the set, a duplicate is found.
Detailed Dry Run
[1, 2, 3, 1] Map {} -> {1} -> {1, 2} -> {1, 2, 3} -> Contains 1! True
Group Anagrams
Given an array of strings 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.
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
"eat", "tea", and "ate" are anagrams. "tan" and "nat" are anagrams. "bat" is alone.
Intuition
Compare every string with every other string to check if they are anagrams. We use a 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 |
Intuition
Instead of nested loops, we use a Hash Map. Since all anagrams share the same sorted version, we sort each string and use it as a key. All strings that result in the same sorted string are added to the same list.
Detailed Dry Run
| Input String | Sorted Key | Action |
|---|---|---|
| "eat" | "aet" | Map["aet"] = ["eat"] |
| "tea" | "aet" | Map["aet"] = ["eat", "tea"] |
| "tan" | "ant" | Map["ant"] = ["tan"] |
Intuition
Instead of sorting each string (), we can use the character frequencies (26 characters) as the Hash Map key. This reduces the transformation time to linear . We represent the count array as a string or tuple to use as a key.
Detailed Dry Run
| 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) |
Logic: key = "#1#0#0...#1". Anagrams share the same key without ever sorting.
Longest Consecutive Sequence
Given an unsorted array of integers nums, return the length of the longest consecutive elements sequence. You must write an algorithm that runs in time.
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
The longest consecutive elements sequence is [1, 2, 3, 4]. Therefore its length is 4.
The longest consecutive elements sequence is [0,1,2,3,4,5,6,7,8].
Intuition
For every number 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 |
Intuition
By sorting the array, consecutive numbers are placed adjacent to each other. We can then find sequences by comparing each element with its predecessor. We must handle duplicate elements by skipping them during the streak calculation.
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 |
Intuition
To achieve linear time, we use a Hash Set for lookups. The key insight is identifying the start of a sequence: a number 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
| 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) |
Visual:
[1] -> [2] -> [3] -> [4] (Sequence identified from 1)
Product of Array Except Self
The product of all elements except 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.
Given an integer array nums, return an array answer such that answer[i] is equal to the product of all the elements of nums except nums[i]. The product of any prefix or suffix of nums is guaranteed to fit in a 32-bit integer. You must write an algorithm that runs in O(n) time and without using the division operator.
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
answer[0] = 234 = 24, answer[1] = 134 = 12, answer[2] = 124 = 8, answer[3] = 123 = 6
Any product that includes 0 will be 0
Intuition
For each index 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 |
Intuition
Any element at 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 |
Intuition
We can optimize space by using the result array itself to store prefix products first. Then, we iterate backwards and maintain a running suffix product to multiply with the stored prefix values. This avoids extra arrays.
Detailed Dry Run
nums = [1, 2, 3, 4]
Forward Pass (Prefix):
res = [1, 1, 2, 6] (Storing products of everything to the left)
Backward Pass (Suffix):
| 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 |
Visual Representation:
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
Given an integer array nums and an integer k, return the k most frequent elements. You may return the answer in any order.
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
1 appears 3 times, 2 appears 2 times. The top 2 most frequent elements are [1, 2].
1 is the only element, so it's the most frequent.
Intuition
Count the frequency of each element using a Hash Map. Convert the map entries into a list and sort the list based on frequencies in descending order. Finally, extract the first k elements.
Detailed Dry Run
| Element | Frequency | Sorted Rank |
|---|---|---|
| 1 | 3 | 1 |
| 2 | 2 | 2 |
| 3 | 1 | 3 |
Intuition
Maintain a Min-Heap of size 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
| 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) |
Intuition
Frequencies range from 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
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] |
Collection (Right to Left):
1 -> res = [1]2 -> res = [1, 2]len(res) == k, STOP.Subarray Sum Equals K
The sum of subarray 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].
Given an array of integers nums and an integer k, return the total number of subarrays whose sum equals to k. A subarray is a contiguous non-empty sequence of elements within an array.
nums = [1, 2, 3], k = 3
Subarrays:
[1, 2] -> Sum = 3 (Valid)
[3] -> Sum = 3 (Valid)
Answer: 2Examples
The subarrays [1,1] and [1,1] have sum 2.
The subarrays [1,2] and [3] have sum 3.
Intuition
We check every possible subarray by iterating through all pairs of start indices 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) |
Avoid the inner sum loop by adding 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 |
Intuition
The sum of subarray 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
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
Since all integers are in the range [1, n], we can use the array itself as a frequency map by negating values at indices corresponding to the numbers seen. If we encounter an already negative value, it's a duplicate.
Given an integer array nums of length n where all the integers of nums are in the range [1, n] and each integer appears once or twice, return an array of all the integers that appears twice. You must solve the problem without using extra space and in O(n) runtime.
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
2 and 3 appear twice in the array.
1 appears twice.
Intuition
We check every element against all subsequent elements to find duplicates. If a match is found, we add it to our result list (handling duplicates in the result list if necessary).
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] |
Intuition
Use an auxiliary Hash Set or boolean array to track which numbers have been seen so far. If a number is already in the set, it's a duplicate.
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] |
Intuition
Since all integers are in the range [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
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!| Num (x) | Index (|x|-1) | Value at Index | Action | Result | | :--- | :--- | :--- | :--- | :--- | | 4 | 3 | 7 | Mark: nums[3]=-7 | [] | | 3 | 2 | 2 | Mark: nums[2]=-2 | [] | | 2 | 1 | 3 | Mark: nums[1]=-3 | [] | | 7 | 6 | 3 | Mark: nums[6]=-3 | [] | | 2 | 1 | -3 | Already Negative! | [2] |
Set Matrix Zeroes
If an element is 0, we must mark its row and column. To do this in-place, we can use the first row and first column of the matrix itself as auxiliary storage (flags).
Given an m x n integer matrix matrix, if an element is 0, set its entire row and column to 0's. You must do it in place.
Matrix:
[1, 1, 1]
[1, 0, 1]
[1, 1, 1]
Output:
[1, 0, 1]
[0, 0, 0]
[1, 0, 1]Examples
The element at (1,1) is 0, so row 1 and column 1 are set to 0.
Elements at (0,0) and (0,3) are 0, so their rows and columns are set to 0.
Intuition
Create a copy of the matrix. If an element in the original is 0, set the entire row and column in the copy to 0. This is simple but uses O(M*N) extra space and involves redundant work.
Detailed Dry Run
matrix = [[1, 0], [1, 1]]
copy = [[1, 0], [1, 1]]copy: Set row 0 to 0s, col 1 to 0s.copy becomes [[0, 0], [1, 0]]matrix with copy values.Intuition
We can improve the space complexity by using two auxiliary boolean arrays: 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 |
Intuition
Use the first row and first column of the matrix itself to store flags for which rows and columns should be zeroed.
row0 and col0 to track if the first row and column themselves need to be zeroed.matrix[i][j] == 0, set matrix[i][0] = 0 and matrix[0][j] = 0.Detailed Dry Run
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
To find two non-overlapping subarrays with lengths 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.
Given an integer array nums and two integers firstLen and secondLen, return the maximum sum of two non-overlapping subarrays with lengths firstLen and secondLen. The subarrays must be non-overlapping, meaning they don't share any index.
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
One choice of subarrays is [9] with length 1, and [6,5] with length 2.
One choice of subarrays is [3,8,1] with length 3, and [8,9] with length 2.
Intuition
Iterate through all possible start positions for the first subarray of length 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 |
Intuition
Use prefix sums to calculate any subarray sum in O(1) time. This eliminates the inner loop for sum calculation but still requires O(N²) to check all pairs of non-overlapping subarrays.
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 |
Intuition
Maintain the maximum sum of a subarray of length 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
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
Merging multiple sorted arrays can be done efficiently using a Min-Heap. By keeping the current element of each array in the heap, we can always extract the smallest element in O(log K) time. Alternatively, Divide and Conquer can merge them in pairs.
You are given k sorted arrays. Merge all the arrays into one sorted array and return it.
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
Merging the three lists: [1,4,5], [1,3,4], [2,6] results in [1,1,2,3,4,4,5,6].
Empty list.
Intuition
Collect all elements from all K arrays into a single list, sort that list, and return it. This is simple but doesn't take advantage of the fact that each individual array is already sorted.
Detailed Dry Run
| All Elements | Sorted Result |
|---|---|
| [1,4,5, 1,3,4, 2,6] | [1,1,2,3,4,4,5,6] |
Intuition
Merge the K arrays in pairs. In the first pass, merge array 1 with 2, 3 with 4, etc. Repeat this process until only one sorted array remains. This reduces the number of arrays logarithmically (log K).
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] |
Intuition
Use a Min-Heap to store the first element of each array along with its source array index and element index. Repeatedly extract the minimum element and insert the next element from the same array into the heap.
Detailed Dry Run
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
The missing positive must be in the range [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.
Given an unsorted integer array nums, return the smallest missing positive integer. You must implement an algorithm that runs in O(n) time and uses constant extra space.
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
The numbers in the range [1,2] are all in the array. 3 is the smallest missing positive.
1 is in the array, but 2 is missing.
Intuition
Sort the input array. Since we are looking for the smallest missing positive integer (starting from 1), iterate through the sorted array and keep track of a 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 | - | - |
Intuition
Store all numbers in a HashSet for O(1) lookups. Iterate from 1 up to 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 |
Intuition
Use the array itself as a 'hash map'. Since the answer must be in the range [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
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)
To achieve average time for all operations, we combine a HashMap and an ArrayList. The ArrayList stores the values for random access, while the HashMap stores value-to-index mappings for lookups and deletions. Deletion is optimized by swapping the target element with the last element in the list.
Implement the RandomizedSet class: bool insert(int val) Inserts an item val into the set if not present. Returns true if the item was not present, false otherwise. bool remove(int val) Removes an item val from the set if present. Returns true if the item was present, false otherwise. int getRandom() Returns a random element from the current set of elements. Each element must have the same probability of being returned. All operations must be O(1) average time complexity.
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
RandomizedSet randomizedSet = new RandomizedSet(); randomizedSet.insert(1); // Inserts 1 to the set. Returns true as 1 was inserted successfully. randomizedSet.remove(2); // Returns false as 2 does not exist in the set. randomizedSet.insert(2); // Inserts 2 to the set, returns true. randomizedSet.getRandom(); // getRandom() should return either 1 or 2 randomly. randomizedSet.remove(1); // Removes 1 from the set, returns true. randomizedSet.insert(2); // 2 was already in the set, so return false. randomizedSet.getRandom(); // Since 2 is the only number in the set, getRandom() will always return 2.
Intuition
Use a simple dynamic array (ArrayList/vector) to store elements. For 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 |
Intuition
A HashSet allows 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) |
Intuition
To achieve O(1) for all operations, we combine a HashMap and a dynamic array (List). The List stores elements for O(1) random access, and the HashMap stores 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
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
Counting smaller elements to the right can be solved by iterating backwards and using a data structure that supports 'insert' and 'count elements smaller than X'. Candidates include Binary Indexed Trees (BIT), Segment Trees, or modifying Merge Sort to track displacements. The Merge Sort approach is highly efficient as it naturally counts how many times an element from the right half is moved before an element from the left half.
You are given an integer array nums and you have to return a new counts array. The counts array has the property where counts[i] is the number of smaller elements to the right of nums[i].
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
To the right of 5 there are 2 smaller elements (2 and 1). To the right of 2 there is 1 smaller element (1). To the right of 6 there is 1 smaller element (1). To the right of 1 there is 0 smaller element.
Intuition
For each element at index 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
nums = [5, 2, 6, 1]
Intuition
Iterate through the array from right to left. For each element, we want to know how many elements we've already seen that are smaller than it. We can use a Binary Indexed Tree (BIT) to store the frequencies of the elements seen so far. Since the numbers can be large or negative, we use Coordinate Compression to map them to a small range [1, unique_elements].
Detailed Dry Run
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]Intuition
This approach uses the property of Merge Sort: while merging two sorted halves 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
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
Determine if a 9 x 9 Sudoku board is valid. Only the filled cells need to be validated according to the following rules:
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
Intuition
Check rows, columns, and 3x3 boxes separately. For each check, use a boolean array or set to detect duplicates.
Detailed Dry Run
Check Row 0: [5, 3, ., 7, .] -> Unique numbers. OK. Check Col 0: [5, 6, ., 8, 4...] -> Unique. OK.
Intuition
Iterate over the board once. Maintain arrays of HashSets for rows, columns, and boxes to track seen digits.
Detailed Dry Run
i=4, j=4, val='5'
Intuition
Use an integer (32-bit) as a bitmask for each row, column, and box. The -th bit represents the presence of digit .
Detailed Dry Run
i=0, val='5'. mask = 1 << 5 (binary 00100000). Set bit in row[0], col[0], box[0].
Encode and Decode Strings
Design an algorithm to encode a list of strings to a string. The encoded string is then sent over the network and is decoded back to the original list of strings.
Input: ["hello", "world"]
Encoding: length + '#' + string
"hello" -> "5#hello"
"world" -> "5#world"
Result: "5#hello5#world"
Decoding: Read length, skip '#', read N chars.Examples
Intuition
Join strings with a semicolon or other delimiter. This fails if a string itself contains the delimiter.
Detailed Dry Run
["a;b", "c"] -> "a;b;c" -> Decodes to ["a", "b", "c"] (Error!)
Intuition
Prefix each string with its length followed by a special character (e.g., '#'). When decoding, read the length, skip '#' and take that many characters.
Detailed Dry Run
["leet", "code"] -> "4#leet4#code" Read 4, skip #, read 'leet'. Read 4, skip #, read 'code'.
Intuition
To handle any characters including delimiters and lengths, use an escaping mechanism or standard Base64 encoding. Alternatively, use a fixed-width length prefix (e.g., 4 bytes) for true binary safety.
Detailed Dry Run
Input: ["3#a", "b"] Encoding: Escape # as ##. Use # as delimiter. Result: "3##a#b#" Decoding: Unescape ## back to #.
Brick Wall
There is a rectangular brick wall in front of you. The wall is n rows high and the width is the same for each row. You want to draw a vertical line from the top to the bottom and cross the least number of bricks. Each row is represented by a list of brick widths. A line crosses a brick if it passes through its interior, but not its edge.
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
Intuition
Check every possible vertical line position (at every 0.5 unit width up to total width). For each position, count how many bricks are crossed. Extremely slow.
Detailed Dry Run
Width=6. Check 1, 2, 3, 4, 5. Line at 3: Row1 (edge), Row2 (edge)... Crosses 0 bricks in those rows.
Intuition
A line crosses the least bricks if it passes through the most edges. We use a HashMap to count the frequency of edges (prefix sums) at each position (excluding the far right edge).
Detailed Dry Run
Row: [1, 2, 2, 1] -> Edges: {1:1, 3:1, 5:1} Row: [3, 1, 2] -> Edges: {1:1, 3:2, 5:1, 4:1} Max edges = 2 (at pos 3). Min bricks = Rows(2) - Max(2) = 0.
Intuition
Similar to merging K sorted lists, we can treat each row's edges as a sorted list. Use pointers to advance through edges across all rows, counting occurrences at each position. This is more efficient if the number of bricks is massive but rows are few.
Detailed Dry Run
Row 1: [1, 2, 2, 1] -> Edges: [1, 3, 5] Row 2: [3, 1, 2] -> Edges: [3, 4] Initially: ptrs=[0, 0]. Curr Edges: [1, 3]. Min is 1. Pop 1. Next min is 3. Pop 3 from both. Count=2.
Isomorphic Strings
Two 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.
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
Intuition
A character in 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
s="ab", t="aa" m1: {a: a}, m2: {a: a} m1: {b: a}, m2 already has {a: a}! Conflict.
Intuition
Characters are isomorphic if they appear at the same relative indices throughout the string. We track the 'last seen' index for each character.
Detailed Dry Run
s="egg", t="add" e(0), a(0) -> Match g(1), d(1) -> Match g(2), d(2) -> Match
Intuition
Convert both strings to a 'canonical' or 'normal' form where each character is replaced by the index of its first appearance. If both strings produce the same normalized form, they are isomorphic.
Detailed Dry Run
s = "paper" -> First seen indices: p:0, a:1, e:3, r:4. Normalized: [0, 1, 0, 3, 4] t = "title" -> First seen indices: t:0, i:1, l:3, e:4. Normalized: [0, 1, 0, 3, 4] Match!
Continuous Subarray Sum
Given an integer array 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.
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
Intuition
Iterate through all possible subarrays and calculate their sum. If any sum is a multiple of k and length is , return true.
Detailed Dry Run
i=0, j=1: sum=23+2=25 (Not multiple of 6) i=1, j=2: sum=2+4=6 (Multiple of 6!) return true
Intuition
Use a prefix sum array to calculate the sum of any subarray in O(1). Iterate through all pairs with and check if .
Detailed Dry Run
nums=[23, 2, 4], prefix=[0, 23, 25, 29] i=0, j=1: (25-0)%6 = 1 i=1, j=2: (29-23)%6 = 0 (Match!)
Intuition
If the remainder of is the same as , then the subarray sum from to is a multiple of . We use a HashMap to store remainder -> first_seen_index and check if current_index - first_seen_index >= 2.
Detailed Dry Run
nums=[23, 2, 4], k=6. Map: {0: -1}
Help us improve! Report bugs or suggest new features on our Telegram group.