Some array problems don't belong to a broad pattern. One specific trick solves each of them, and that trick is hard to invent under pressure but easy to remember once you've seen it. This note collects four of them. Each runs in time (or per operation) with extra space.
How to spot them#
Since each trick solves a narrow kind of problem, the hard part is knowing when to use it. Each one has a recognizable prompt:
| Prompt | Trick |
|---|---|
| "Element that appears more than ⌊n/2⌋ (or ⌊n/3⌋) times", space | Boyer-Moore voting |
| Sort an array of only three distinct values in one pass | Dutch national flag |
Rotate an array by k in place |
Three reversals |
| Insert, delete and get-random all in | Hashmap + list, swap with the last element |
Boyer-Moore majority vote#
The first trick finds a majority value without counting every value. Think of it as cancellation. Pair each vote for the current candidate against a vote for something else and cross both out. A value that holds more than half the votes can't be fully canceled, so it is the one left standing.
- Keep a
candidateand acount. - If
countis 0, adopt the current value as the new candidate. - Add 1 for a matching value, subtract 1 otherwise.
The vote only finds the value that survives. When the problem does not guarantee that a majority exists, make a second pass to check that the survivor appears often enough.
Dutch national flag#
The second trick sorts a three-valued array in a single pass, without counting. Three pointers split the array into four zones: [0, low) holds 0s, [low, mid) holds 1s, [mid, high] is unknown, and (high, n) holds 2s. Look at nums[mid] and shrink the unknown zone by one each step: a 0 swaps down to low, a 1 stays, and a 2 swaps up to high.
Template#
The vote and the flag both fit in a few lines of pseudocode. First the vote:
candidate = none, count = 0
for each x in items:
if count == 0:
candidate = x # everything before has canceled out
if x == candidate: count += 1
else: count -= 1
return candidate # verify it if a majority is not promised
Then the three-zone partition:
low = mid = 0, high = n - 1
while mid <= high:
if items[mid] is in the low group:
swap items[low] and items[mid]
low += 1, mid += 1 # what came from low is already known
else if items[mid] is in the middle group:
mid += 1
else:
swap items[mid] and items[high]
high -= 1 # keep mid: the new value is unseen
Rotate by reversing#
The last two tricks are about moving elements in place instead of copying them. To rotate an array to the right by k, reverse the whole array, then reverse the first k elements, then reverse the rest.
Here's what each reversal does on a small example:
| Step | [1, 2, 3, 4, 5], k = 2 |
|---|---|
| Reverse all | [5, 4, 3, 2, 1] |
| Reverse first k | [4, 5, 3, 2, 1] |
| Reverse the rest | [4, 5, 1, 2, 3] |
The first reversal moves the last k elements to the front, but backwards. The two partial reversals put each block back in order. The same "reverse fixes the orientation" idea rotates a matrix (see Math & Bits).
Delete in O(1): swap with the last#
A set with get-random needs a list for random access and a hashmap from value to index for lookups. The catch is that deleting from the middle of a list is , so don't. Instead:
- Look up the index
iof the value to delete. - Move the last element into slot
i, and update its index in the hashmap. - Pop the last slot and delete the value from the hashmap.
Order inside the list doesn't matter for get-random, which is what makes the swap legal.
Pitfalls#
These are the mistakes that most often cost a correct answer.
- Dutch flag: advancing
midafter swapping withhigh. The value that arrived from the right hasn't been examined yet. - Boyer-Moore without verification. Candidates always survive the vote, even when none of them is frequent enough. Count them again when the majority is not guaranteed.
- Rotation with
k > n. Reducekfirst, or the reversal bounds are wrong. - Swap-delete on the last element itself. The swap is then a no-op, so the order of the map updates matters.
Problems in this pattern#
These problems use this pattern. Solve them before you open the hints below. Start with the first three.
- 169. Majority Element
- 229. Majority Element II
- 75. Sort Colors
- 189. Rotate Array
- 380. Insert Delete GetRandom O(1)
- 48. Rotate Image
Hints#
Open a hint only after you have tried the problem for a while.
Hint: Majority Element
The problem guarantees a majority, so one candidate and one count are enough. No second pass is needed.
Hint: Majority Element II
At most two values can appear more than ⌊n/3⌋ times. Keep two candidates and two counts; a value that matches neither decrements both. Two candidates always survive, so make a second pass to count each one.
Hint: Sort Colors
This is the three-zone partition with 0, 1 and 2 as the groups. After a swap with high, look at nums[mid] again before you move on.
Hint: Rotate Array
Use the three reversals in place. Take k %= n first.
Hint: Insert Delete GetRandom O(1)
Keep a list of values and a map from value to index. On remove, move the last value into the hole. Update the moved value's index first and delete the removed value's map entry last; the other order re-inserts the deleted value when it was the last one.
Hint: Rotate Image
Transpose the matrix, then reverse each row. The full trick is in the matrix note linked above.
Key takeaways#
- Boyer-Moore: a majority survives pairwise cancellation; with two candidates, verify at the end.
- Dutch flag: four zones, three pointers; after swapping with
high, re-examinemid. - Rotation by
kis three reversals. - delete from a list: move the last element into the hole and fix the index map.