Keep each value at most twice
Given an integer array nums sorted in non-decreasing order, remove extra copies in place
so that each value appears at most twice, keeping the order. Use O(1) extra memory. Return the kept prefix of the
array.
Examples
| Input | Output | Why |
|---|---|---|
nums = [1, 1, 1, 2, 2, 3] | [1, 1, 2, 2, 3] | |
nums = [0, 0, 1, 1, 1, 1, 2, 3, 3] | [0, 0, 1, 1, 2, 3, 3] |
Constraints
- 1 ≤ n ≤ 3 × 10⁴
- −10⁴ ≤ nums[i] ≤ 10⁴
- nums is sorted in non-decreasing order
Hints
Hint 1
With "at most once", you write a value when it differs from the last value written. What changes for "at most twice"?
Hint 2
Copies of a value are next to each other. A third copy is exactly a value equal to the one written two places earlier.
Hint 3
Keep a write index w. Write nums[r] at w when w < 2 or nums[r] differs from nums[w − 2].
Where people lose marks
- Comparing with nums[r − 2] instead of nums[w − 2]. The reader's neighbours may already have been dropped; only the kept prefix tells you how many copies are kept.
- Counting copies with a separate counter that is not reset when the value changes.
Dry run on [3,3,3,3,5,6,6,6]
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 10nums30w/r31323354656667w0
w is the length of the kept prefix. A value is kept unless nums[w − 2] already equals it.
-
Step 2 / 10nums30w/r31323354656667w1r0
w = 0 < 2: keep 3.
-
Step 3 / 10nums3031w/r323354656667w2r1
w = 1 < 2: keep 3.
-
Step 4 / 10nums303132w/r3354656667w2r2
nums[r] = 3 = nums[w − 2]: two copies are already kept. Skip it.
-
Step 5 / 10nums303132w33r54656667w2r3
nums[r] = 3 = nums[w − 2]: two copies are already kept. Skip it.
-
Step 6 / 10nums303152w3354r656667w3r4
nums[r] = 5 ≠ nums[w − 2] = 3: keep it at position 2.
-
Step 7 / 10nums30315263w5465r6667w4r5
nums[r] = 6 ≠ nums[w − 2] = 3: keep it at position 3.
-
Step 8 / 10nums3031526364w6566r67w5r6
nums[r] = 6 ≠ nums[w − 2] = 5: keep it at position 4.
-
Step 9 / 10nums303152636465w6667rw5r7
nums[r] = 6 = nums[w − 2]: two copies are already kept. Skip it.
-
Step 10 / 10nums3031526364w5
The kept prefix has length 5: 3, 3, 5, 6, 6.
How to think about it
The reader r visits every element; the writer w is the length of the kept prefix.
Because the array is sorted, the copies of a value sit together, and the kept prefix is sorted too. A value
x may be kept unless the prefix already ends in two copies of x, and in a sorted prefix
that is exactly the condition nums[w − 2] = x.
So: if w < 2 or nums[r] ≠ nums[w − 2], copy nums[r] to position
w and advance w. Replace 2 by k and the same loop keeps each value at most k times.
Complexity. Time O(n) · Space O(1)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def keep_at_most_twice(nums):
w = 0 # nums[:w] is the kept prefix
for r in range(len(nums)):
if w < 2 or nums[r] != nums[w - 2]:
nums[w] = nums[r]
w += 1
return nums[:w]#include <vector>
std::vector<int> solve(std::vector<int> nums) {
size_t w = 0; // nums[0..w) is the kept prefix
for (size_t r = 0; r < nums.size(); ++r)
if (w < 2 || nums[r] != nums[w - 2]) nums[w++] = nums[r];
nums.resize(w);
return nums;
}class Solution {
public int[] keepAtMostTwice(int[] nums) {
int w = 0; // nums[0..w) is the kept prefix
for (int r = 0; r < nums.length; r++) {
if (w < 2 || nums[r] != nums[w - 2]) nums[w++] = nums[r];
}
return java.util.Arrays.copyOf(nums, w);
}
}function keepAtMostTwice(nums) {
let w = 0; // nums[0..w) is the kept prefix
for (let r = 0; r < nums.length; r++) {
if (w < 2 || nums[r] !== nums[w - 2]) nums[w++] = nums[r];
}
return nums.slice(0, w);
}fn solve(nums: &[i64]) -> Vec<i64> {
let mut v = nums.to_vec();
let mut w = 0; // v[..w] is the kept prefix
for r in 0..v.len() {
if w < 2 || v[r] != v[w - 2] { v[w] = v[r]; w += 1; }
}
v.truncate(w);
v
}-- Build the kept list back to front: a value is dropped when the
-- second most recent kept value already equals it.
solve :: [Int] -> [Int]
solve = reverse . foldl step []
where
step kept@(_ : prev : _) x | x == prev = kept
step kept x = x : kept
Variants to try
- At most k copies: compare with nums[w − k].
- The array is not sorted: the copies are no longer adjacent, and a hash map of counts is needed.
Next problems
A palindrome, ignoring punctuation and case
Compare from both ends inward, stepping over anything that is not a letter or a digit. No cleaned copy of the string is needed.
Open the problem → Easy10 minMerge two sorted arrays
The step at the heart of merge sort: one pointer per array, and the smaller front value goes next.
Open the problem → Easy10 minMove the zeros to the end
One pointer reads every element, the other marks where the next non-zero belongs. The order of the non-zero values is kept.
Open the problem →Write a solution and run it against the real test table.
Keep going
A palindrome, ignoring punctuation and case
Compare from both ends inward, stepping over anything that is not a letter or a digit. No cleaned copy of the string is needed.
Open the problem → Easy10 minMerge two sorted arrays
The step at the heart of merge sort: one pointer per array, and the smaller front value goes next.
Open the problem → Easy10 minMove the zeros to the end
One pointer reads every element, the other marks where the next non-zero belongs. The order of the non-zero values is kept.
Open the problem → Two pointersThe pattern behind it
Two indices walking a sorted or paired structure, each step ruling out one candidate for good.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.