Remove Duplicates from Sorted Array II
MedPrompt
Given an integer array nums sorted in non-decreasing order, remove some duplicates in-place such that each unique element appears at most twice. The relative order of the elements should be kept the same.
Since it is impossible to change the length of the array in some languages, you must instead have the result be placed in the first part of the array nums. More formally, if there are k elements after removing the duplicates, then the first k elements of nums should hold the final result. It does not matter what you leave beyond the first k elements.
Return k after placing the final result in the first k slots of nums.
Do not allocate extra space for another array. You must do this by modifying the input array in-place with O(1) extra memory.
Custom Judge:
The judge will test your solution with the following code:
int[] nums = [...]; // Input array
int[] expectedNums = [...]; // The expected answer with correct length
int k = removeDuplicates(nums); // Calls your implementation
assert k == expectedNums.length;
for (int i = 0; i < k; i++) {
assert nums[i] == expectedNums[i];
}If all assertions pass, then your solution will be accepted.
Example 1:
Input: nums = [1,1,1,2,2,3]
Output: 5, nums = [1,1,2,2,3,_]
Explanation: Your function should return k = 5, with the first five elements of nums being 1, 1, 2, 2 and 3 respectively.
It does not matter what you leave beyond the returned k (hence they are underscores).Example 2:
Input: nums = [0,0,1,1,1,1,2,3,3]
Output: 7, nums = [0,0,1,1,2,3,3,_,_]
Explanation: Your function should return k = 7, with the first seven elements of nums being 0, 0, 1, 1, 2, 3 and 3 respectively.
It does not matter what you leave beyond the returned k (hence they are underscores).
Constraints:
1 <= nums.length <= 3 * 104-104 <= nums[i] <= 104numsis sorted in non-decreasing order.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach iterates through the array, identifies groups of duplicate elements, and if a group has more than two elements, it manually shifts the rest of the array to overwrite the excess duplicates. This is an intuitive but inefficient way to solve the problem in-place.
Algorithm
- Initialize a pointer
i = 0and the effective lengthn = nums.length. - Loop while
iis less thann. - Inside the loop, use another pointer
jstarting fromito find the end of the contiguous block of elements equal tonums[i]. - Calculate the
countof these elements (j - i). - If
count > 2, it means there arecount - 2excess duplicates.- Shift all elements from index
jton-1to the left bycount - 2positions. - Decrease the effective length
nbycount - 2. - Advance
iby 2, since we've kept two elements.
- Shift all elements from index
- If
count <= 2, no elements are removed. Advanceitojto process the next distinct number. - After the loop terminates, return the final effective length
n.
Walkthrough
The core idea is to process the array in chunks of identical numbers. We use a pointer i to mark the beginning of a chunk and another pointer j to find its end. The number of elements in the chunk is count = j - i.
If count is greater than 2, we have count - 2 excess elements that need to be removed. We achieve this by shifting all subsequent elements (from index j to the end) to the left by count - 2 positions. This overwrites the unwanted duplicates. After shifting, we must also decrease the effective length of the array. The pointer i is then advanced by 2, as we keep two elements from the current chunk.
If count is 2 or less, no removal is needed, and we simply advance i to j to start processing the next chunk of numbers.
This method is considered brute-force because the shifting operation can be very time-consuming, especially for arrays with many duplicates at the beginning.
class Solution { public int removeDuplicates(int[] nums) { int i = 0; int n = nums.length; while (i < n) { // Find the end of the block of duplicates int j = i; while (j < n && nums[j] == nums[i]) { j++; } int count = j - i; if (count > 2) { int numToRemove = count - 2; // Shift elements to the left for (int k = j; k < n; k++) { nums[k - numToRemove] = nums[k]; } // Update the effective length n -= numToRemove; // Move i to the next position after the two kept elements i += 2; } else { // Move to the next distinct number i = j; } } return n; }}Complexity
Time
O(N^2)
Space
O(1)
Trade-offs
Pros
It correctly solves the problem in-place, adhering to the O(1) extra space requirement.
Cons
Highly inefficient with a time complexity of O(N^2) in the worst-case scenario.
The logic for index manipulation and shifting is complex and can be difficult to implement correctly.
This is an optimal and elegant approach that solves the problem in a single pass using two pointers. A 'slow' pointer, k, tracks the position for the next valid element, while a 'fast' pointer, i, scans the array. This method avoids costly shifting operations by directly overwriting invalid elements.
Algorithm
- Handle the edge case where the array has 2 or fewer elements by returning its length.
- Initialize a slow pointer
k = 2, as the first two elements are always potentially part of the result. - Initialize a fast pointer
i = 2to iterate through the rest of the array. - Loop with
ifrom 2 to the end of the array. - In each iteration, check if the current element
nums[i]is different from the element atnums[k-2].- The element
nums[k-2]is the first of the last-seen pair of numbers in the valid part of the array.
- The element
- If
nums[i] != nums[k-2], it meansnums[i]is not a third duplicate. So, place it at the next valid position:nums[k] = nums[i], and then incrementk. - If
nums[i] == nums[k-2], it's a third (or more) duplicate, so we do nothing and just advance the fast pointeri. - After the loop,
kwill be the length of the modified array. Returnk.
Walkthrough
The two-pointer technique is ideal for in-place array modifications. We maintain a slow pointer k that represents the boundary of the processed, valid part of the array. All elements before index k (i.e., nums[0...k-1]) satisfy the condition that each number appears at most twice.
A fast pointer i iterates through the array from the beginning to the end. For each element nums[i], we decide if it should be part of the valid subarray.
The condition for including nums[i] at position k is based on the fact that the array is sorted. An element nums[i] should be kept if it's not the third consecutive identical number. We can check this by comparing nums[i] with the element at nums[k-2]. If nums[i] is different from nums[k-2], it cannot be a third duplicate. This simple check works because if nums[i] were a third duplicate, it would be equal to nums[k-1] and nums[k-2]. The check also naturally handles new numbers and the first two elements of the array (by initializing k appropriately).
We initialize k=2 and start the fast pointer i from 2. If nums[i] is different from nums[k-2], we copy nums[i] to nums[k] and increment k. This efficiently builds the result array at the beginning of nums.
class Solution { public int removeDuplicates(int[] nums) { // If the array has 2 or fewer elements, no duplicates need to be removed. if (nums.length <= 2) { return nums.length; } // k is the slow pointer. The first two elements are always kept. int k = 2; // i is the fast pointer, starting from the third element. for (int i = 2; i < nums.length; i++) { // Compare the current element with the element at k-2. // If they are different, it means the current element is not a third duplicate. if (nums[i] != nums[k - 2]) { nums[k] = nums[i]; k++; } } return k; }}Complexity
Time
O(N)
Space
O(1)
Trade-offs
Pros
Extremely efficient with a linear time complexity of O(N).
It's an in-place algorithm with O(1) space complexity.
The logic is concise, easy to reason about, and robust.
Cons
This approach is optimal, so it has no significant disadvantages.
Solutions
Solution
public class Solution { public int RemoveDuplicates(int[] nums) { int k = 0; foreach(int x in nums) { if (k < 2 || x != nums[k - 2]) { nums[k++] = x; } } return k; }}class Solution {public int removeDuplicates(int[] nums) { int k = 0; for (int x : nums) { if (k < 2 || x != nums[k - 2]) { nums[k++] = x; } } return k; }}/** * @param {number[]} nums * @return {number} */ var removeDuplicates = function (nums) { let k = 0; for (const x of nums) { if (k < 2 || x !== nums[k - 2]) { nums[k++] = x; } } return k; };class Solution {public: int removeDuplicates(vector<int> &nums) { int k = 0; for (int x : nums) { if (k < 2 || x != nums[k - 2]) { nums[k++] = x; } } return k; }};class Solution: def removeDuplicates(self, nums: List[int]) -> int: k = 0 for x in nums: if k < 2 or x != nums[k - 2]: nums[k] = x k += 1 return kVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.