Reference: LeetCode
Difficulty: Easy
My Post: Java Solutions with Explanation (One Pointer & Two Pointers)
Problem
Given an array
nums
and a valueval
, remove all instances of that value in-place and return the new length.
Note:
- Do not allocate extra space for another array, you must do this by modifying the input array in-place with O(1) extra memory.
- The order of elements can be changed. It doesn’t matter what you leave beyond the new length.
Follow up: What happens when the elements to remove are rare?
Example:
1 | nums = [3,2,2,3], val = 3, |
Analysis
One Pointer (better for many invalid elements)
Wow. It is very elegant!
1 | 3 2 2 3 val = 3 |
Check out the example above for why it is correct.
It is better when elements to remove are so many. So the if
would be skipped often.
1 | public int removeElement(int[] nums, int val) { |
Time: $O(N)$
Space: $O(1)$
Two Pointers (rare invalid elements)
This approach is better when elements to remove are rare. So the if
would be skipped often (check out the code later).
We use two pointers lo
and hi
to solve this problem. lo
points to the current element we are examining; hi
is the current position we want to put invalid element at. See the example below for detail.
1 | val = 3 |
Invariant: Consider two subarrays [0, lo)
and (hi, n - 1]
. The first one is for all valid elements and the second one is for all invalid elements we have examined. They won’t be changed in the future.
Someone may ask that by this swapping an invalid element nums[hi] = 3
would go to lo
. It is okay, because if we do swapping we only update hi
. However, lo
stays where it used to be, so the invalid element will be considered in the next round.
Also, if lo
does not point to an invalid element, we just increase lo
by one to examine the next element.
lo < hi
or lo <= hi
:
It is important to decide to use lo < hi
or lo <= hi
as our loop condition. Here is a trick to find out which one is correct. (decided by base-case examination
)
Consider the following example:
1 | // A |
If we use lo < hi
, the loop will not be executed and always return lo
(0) as the length, which is not correct in case B. By observation, if we use lo <= hi
, the above cases are handled very well and we have an invariant that lo
will always be the resulting length.
1 | public int removeElement(int[] nums, int val) { |
Time: $O(N)$
Space: $O(1)$