26. Remove Duplicates from Sorted Array
Problem Description
You are given an integer array nums
that is sorted in non-decreasing order. Your task is to remove duplicates from this array in-place so that each unique element appears only once, while maintaining the relative order of elements. After removing duplicates, you need to return the count of unique elements.
The key requirements are:
- The modification must be done in-place (without using extra array space)
- After your function runs, the first
k
elements ofnums
should contain all unique elements in their original order, wherek
is the number of unique elements - The elements beyond the first
k
positions don't matter - Return the value
k
For example:
-
If
nums = [1,1,2]
, after calling your function:- The first 2 elements of
nums
should be[1,2]
- Return
2
(the count of unique elements)
- The first 2 elements of
-
If
nums = [0,0,1,1,1,2,2,3,3,4]
, after calling your function:- The first 5 elements of
nums
should be[0,1,2,3,4]
- Return
5
(the count of unique elements)
- The first 5 elements of
The judge will verify your solution by checking that:
- The returned value
k
matches the expected number of unique elements - The first
k
elements in the modified array match the expected unique elements in order
Intuition
Since the array is already sorted, duplicate elements will always be adjacent to each other. This is the key insight that makes the problem simpler - we don't need to look through the entire array to check for duplicates, we only need to compare consecutive elements.
The challenge is doing this in-place. We need to somehow "compact" the array by keeping only unique elements at the beginning. Think of it like having two pointers:
- One pointer reads through all elements in the array
- Another pointer marks where to place the next unique element
We can use a variable k
to track the position where the next unique element should be placed. As we iterate through the array:
- If we're at the very first element (
k == 0
), it's definitely unique, so we keep it - For any other element
x
, we compare it with the element at positionk-1
(the last unique element we've kept) - If
x
is different fromnums[k-1]
, thenx
is a new unique element - we place it at positionk
and incrementk
- If
x
equalsnums[k-1]
, it's a duplicate of what we already have, so we skip it
This way, we're essentially building a new array of unique elements within the original array, overwriting from the beginning. The beauty of this approach is that we're always comparing with the last unique element we've found (nums[k-1]
), not the immediately previous element in the original array. This ensures we correctly identify all unique values even when there are multiple consecutive duplicates.
By the end of the traversal, k
tells us exactly how many unique elements we found, and these elements are neatly arranged in the first k
positions of the array.
Learn more about Two Pointers patterns.
Solution Approach
The implementation uses a single-pass algorithm with a two-pointer technique. Here's how the solution works step by step:
Algorithm Setup:
- Initialize a variable
k = 0
to track the position where the next unique element should be placed k
also represents the current length of the processed (unique elements) array
Main Loop:
Iterate through each element x
in the array nums
:
-
Check if element should be kept:
- If
k == 0
(empty processed array), the current element is the first one and must be kept - OR if
x != nums[k-1]
(current element is different from the last kept unique element)
- If
-
Place the unique element:
- When either condition above is true, place
x
at positionnums[k]
- Increment
k
by 1 to move to the next available position
- When either condition above is true, place
-
Skip duplicates:
- If
x == nums[k-1]
, the element is a duplicate, so we simply continue to the next iteration without modifyingk
- If
Example walkthrough with nums = [1,1,2]
:
- Initially:
k = 0
- First iteration (
x = 1
):k == 0
is true, sonums[0] = 1
,k = 1
- Second iteration (
x = 1
):k != 0
and1 == nums[0]
, skip this duplicate - Third iteration (
x = 2
):k != 0
and2 != nums[0]
, sonums[1] = 2
,k = 2
- Result:
nums = [1,2,...]
, returnk = 2
Time and Space Complexity:
- Time Complexity:
O(n)
wheren
is the length of the array, as we make a single pass through all elements - Space Complexity:
O(1)
as we only use a constant amount of extra space (the variablek
)
The elegance of this solution lies in using the array itself as the storage for the result, overwriting from the beginning while maintaining the relative order of unique elements.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [0,0,1,1,1,2,2,3,3,4]
:
Initial Setup:
k = 0
(position for next unique element)- Array:
[0,0,1,1,1,2,2,3,3,4]
Iteration 1 (x = 0, index 0):
- Check:
k == 0
? Yes, this is our first element - Action: Place 0 at position k β
nums[0] = 0
- Update:
k = 1
- Array state:
[0,0,1,1,1,2,2,3,3,4]
(unchanged visually, but k moved)
Iteration 2 (x = 0, index 1):
- Check:
k == 0
? No. Is0 != nums[0]
? No (0 == 0) - Action: Skip this duplicate
- Array state:
[0,0,1,1,1,2,2,3,3,4]
, k remains 1
Iteration 3 (x = 1, index 2):
- Check:
k == 0
? No. Is1 != nums[0]
? Yes (1 β 0) - Action: Place 1 at position k β
nums[1] = 1
- Update:
k = 2
- Array state:
[0,1,1,1,1,2,2,3,3,4]
Iterations 4-5 (x = 1, indices 3-4):
- Both are duplicates of
nums[1]
= 1, so skip both - k remains 2
Iteration 6 (x = 2, index 5):
- Check: Is
2 != nums[1]
? Yes (2 β 1) - Action: Place 2 at position k β
nums[2] = 2
- Update:
k = 3
- Array state:
[0,1,2,1,1,2,2,3,3,4]
Iteration 7 (x = 2, index 6):
- Duplicate of
nums[2]
= 2, skip - k remains 3
Iteration 8 (x = 3, index 7):
- Check: Is
3 != nums[2]
? Yes (3 β 2) - Action: Place 3 at position k β
nums[3] = 3
- Update:
k = 4
- Array state:
[0,1,2,3,1,2,2,3,3,4]
Iteration 9 (x = 3, index 8):
- Duplicate of
nums[3]
= 3, skip - k remains 4
Iteration 10 (x = 4, index 9):
- Check: Is
4 != nums[3]
? Yes (4 β 3) - Action: Place 4 at position k β
nums[4] = 4
- Update:
k = 5
- Final array state:
[0,1,2,3,4,2,2,3,3,4]
Result:
- Return
k = 5
- First 5 elements are
[0,1,2,3,4]
- all unique values in order - Elements after position 4 don't matter
The key insight: we always compare with nums[k-1]
(the last unique element we kept), not with the previous element in the iteration. This ensures we correctly identify unique values even when there are multiple consecutive duplicates.
Solution Implementation
1class Solution:
2 def removeDuplicates(self, nums: List[int]) -> int:
3 # Initialize write pointer to track position for unique elements
4 write_index = 0
5
6 # Iterate through each element in the array
7 for current_num in nums:
8 # Check if this is the first element or if current element is different from previous unique element
9 if write_index == 0 or current_num != nums[write_index - 1]:
10 # Place the unique element at the write position
11 nums[write_index] = current_num
12 # Move write pointer forward
13 write_index += 1
14
15 # Return the count of unique elements
16 return write_index
17
1class Solution {
2 /**
3 * Removes duplicates from a sorted array in-place and returns the new length.
4 * The array is modified to contain unique elements at the beginning.
5 *
6 * @param nums The sorted integer array from which to remove duplicates
7 * @return The number of unique elements in the array
8 */
9 public int removeDuplicates(int[] nums) {
10 // Initialize pointer for the position of unique elements
11 int uniqueIndex = 0;
12
13 // Iterate through each element in the array
14 for (int currentElement : nums) {
15 // Check if this is the first element or if it's different from the previous unique element
16 if (uniqueIndex == 0 || currentElement != nums[uniqueIndex - 1]) {
17 // Place the unique element at the current unique position
18 nums[uniqueIndex] = currentElement;
19 // Move the unique index pointer forward
20 uniqueIndex++;
21 }
22 }
23
24 // Return the count of unique elements
25 return uniqueIndex;
26 }
27}
28
1class Solution {
2public:
3 int removeDuplicates(vector<int>& nums) {
4 // Initialize write pointer to track position for unique elements
5 int writeIndex = 0;
6
7 // Iterate through each element in the array
8 for (int currentNum : nums) {
9 // Add element if it's the first element OR different from the previous unique element
10 if (writeIndex == 0 || currentNum != nums[writeIndex - 1]) {
11 // Place the unique element at the write position and increment
12 nums[writeIndex++] = currentNum;
13 }
14 }
15
16 // Return the count of unique elements
17 return writeIndex;
18 }
19};
20
1/**
2 * Removes duplicates from a sorted array in-place
3 * @param nums - The input sorted array
4 * @returns The number of unique elements
5 */
6function removeDuplicates(nums: number[]): number {
7 // Initialize pointer for the position of unique elements
8 let uniqueCount: number = 0;
9
10 // Iterate through each element in the array
11 for (const currentNum of nums) {
12 // Add element if it's the first element or different from the previous unique element
13 if (uniqueCount === 0 || currentNum !== nums[uniqueCount - 1]) {
14 // Place the unique element at the current unique position
15 nums[uniqueCount] = currentNum;
16 // Increment the unique element counter
17 uniqueCount++;
18 }
19 }
20
21 // Return the total count of unique elements
22 return uniqueCount;
23}
24
Time and Space Complexity
Time Complexity: O(n)
- The algorithm iterates through the input array
nums
exactly once using a single for loop - Each iteration performs constant time operations: comparison (
x != nums[k - 1]
), assignment (nums[k] = x
), and increment (k += 1
) - Since we visit each of the
n
elements exactly once withO(1)
work per element, the total time complexity isO(n)
Space Complexity: O(1)
- The algorithm modifies the input array in-place without using any additional data structures
- Only a constant amount of extra space is used for the variable
k
(counter) and the loop variablex
- The space usage does not scale with the input size, resulting in
O(1)
auxiliary space complexity
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Incorrectly Comparing with the Wrong Element
A frequent mistake is comparing the current element with nums[write_index]
instead of nums[write_index - 1]
:
Incorrect Code:
def removeDuplicates(self, nums: List[int]) -> int:
write_index = 0
for current_num in nums:
# WRONG: Comparing with nums[write_index] instead of nums[write_index - 1]
if write_index == 0 or current_num != nums[write_index]:
nums[write_index] = current_num
write_index += 1
return write_index
Why this fails: nums[write_index]
hasn't been set yet or contains an old value that will be overwritten. You need to compare with the last placed unique element at nums[write_index - 1]
.
2. Using a Separate Read Pointer Unnecessarily
Some attempt to use two explicit pointers, making the code more complex:
Overly Complex Code:
def removeDuplicates(self, nums: List[int]) -> int:
if not nums:
return 0
read_ptr = 1 # Unnecessary separate read pointer
write_ptr = 1
while read_ptr < len(nums):
if nums[read_ptr] != nums[write_ptr - 1]:
nums[write_ptr] = nums[read_ptr]
write_ptr += 1
read_ptr += 1
return write_ptr
Better approach: Use the for loop's implicit iteration as the read pointer, keeping only the write pointer explicit.
3. Forgetting Edge Cases
Incorrect handling of empty array:
def removeDuplicates(self, nums: List[int]) -> int:
write_index = 1 # WRONG: Assumes at least one element
for i in range(1, len(nums)): # Skips first element
if nums[i] != nums[write_index - 1]:
nums[write_index] = nums[i]
write_index += 1
return write_index
Why this fails: This crashes on empty arrays and unnecessarily complicates the logic by starting from index 1.
4. Modifying the Array While Iterating Incorrectly
Attempting to delete elements:
def removeDuplicates(self, nums: List[int]) -> int:
i = 1
while i < len(nums):
if nums[i] == nums[i-1]:
nums.pop(i) # WRONG: Not in-place as required
else:
i += 1
return len(nums)
Why this fails: Using pop()
or del
operations has O(n) time complexity for each removal, making the overall complexity O(nΒ²). Also, this approach doesn't meet the "in-place with O(1) space" requirement as pop()
operations can trigger array resizing.
5. Off-by-One Errors in Index Checking
Incorrect boundary check:
def removeDuplicates(self, nums: List[int]) -> int:
write_index = 0
for current_num in nums:
# WRONG: Should check write_index == 0, not write_index <= 0
if write_index < 1 or current_num != nums[write_index - 1]:
nums[write_index] = current_num
write_index += 1
return write_index
Why this matters: While write_index < 1
works functionally, it's less clear in intent. Using write_index == 0
explicitly shows you're checking for the first element case.
Correct Solution Pattern:
- Always compare with
nums[write_index - 1]
(the last placed unique element) - Handle the first element with
write_index == 0
check - Use a single write pointer with the for loop as implicit read pointer
- Never use operations that modify array size (pop, del, insert)
What's the output of running the following function using input [30, 20, 10, 100, 33, 12]
?
1def fun(arr: List[int]) -> List[int]:
2 import heapq
3 heapq.heapify(arr)
4 res = []
5 for i in range(3):
6 res.append(heapq.heappop(arr))
7 return res
8
1public static int[] fun(int[] arr) {
2 int[] res = new int[3];
3 PriorityQueue<Integer> heap = new PriorityQueue<>();
4 for (int i = 0; i < arr.length; i++) {
5 heap.add(arr[i]);
6 }
7 for (int i = 0; i < 3; i++) {
8 res[i] = heap.poll();
9 }
10 return res;
11}
12
1class HeapItem {
2 constructor(item, priority = item) {
3 this.item = item;
4 this.priority = priority;
5 }
6}
7
8class MinHeap {
9 constructor() {
10 this.heap = [];
11 }
12
13 push(node) {
14 // insert the new node at the end of the heap array
15 this.heap.push(node);
16 // find the correct position for the new node
17 this.bubble_up();
18 }
19
20 bubble_up() {
21 let index = this.heap.length - 1;
22
23 while (index > 0) {
24 const element = this.heap[index];
25 const parentIndex = Math.floor((index - 1) / 2);
26 const parent = this.heap[parentIndex];
27
28 if (parent.priority <= element.priority) break;
29 // if the parent is bigger than the child then swap the parent and child
30 this.heap[index] = parent;
31 this.heap[parentIndex] = element;
32 index = parentIndex;
33 }
34 }
35
36 pop() {
37 const min = this.heap[0];
38 this.heap[0] = this.heap[this.size() - 1];
39 this.heap.pop();
40 this.bubble_down();
41 return min;
42 }
43
44 bubble_down() {
45 let index = 0;
46 let min = index;
47 const n = this.heap.length;
48
49 while (index < n) {
50 const left = 2 * index + 1;
51 const right = left + 1;
52
53 if (left < n && this.heap[left].priority < this.heap[min].priority) {
54 min = left;
55 }
56 if (right < n && this.heap[right].priority < this.heap[min].priority) {
57 min = right;
58 }
59 if (min === index) break;
60 [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61 index = min;
62 }
63 }
64
65 peek() {
66 return this.heap[0];
67 }
68
69 size() {
70 return this.heap.length;
71 }
72}
73
74function fun(arr) {
75 const heap = new MinHeap();
76 for (const x of arr) {
77 heap.push(new HeapItem(x));
78 }
79 const res = [];
80 for (let i = 0; i < 3; i++) {
81 res.push(heap.pop().item);
82 }
83 return res;
84}
85
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!