Remove Element
This post explores multiple solutions for removing all occurrences of a specific value from an integer array in-place. Building on the brute force, fast-slow pointer and left-right pointer techniques.
Problem Definition
LeetCode Link: 27. Remove Element
Given an integer array nums
and an integer val
, remove all occurrences of val
in nums
in-place. The order of the elements may be changed. Then return the number of elements in nums
which are not equal to val
.
Consider the number of elements in nums
which are not equal to val
be k
, to get accepted, you need to do the following things:
- Change the array
nums
such that the firstk
elements ofnums
contain the elements which are not equal toval
. The remaining elements ofnums
are not important as well as the size ofnums
. - Return
k
.
Custom Judge:
The judge will test your solution with the following code:
int[] nums = [...]; // Input array
int val = ...; // Value to remove
int[] expectedNums = [...]; // The expected answer with correct length.
// It is sorted with no values equaling val.
int k = removeElement(nums, val); // Calls your implementation
assert k == expectedNums.length;
sort(nums, 0, k); // Sort the first k elements of nums
for (int i = 0; i < actualLength; i++) {
assert nums[i] == expectedNums[i];
}
If all assertions pass, then your solution will be accepted.
Example 1:
Input: nums = [3,2,2,3], val = 3
Output: 2, nums = [2,2,_,_]
Explanation: Your function should return k = 2, with the first two elements of nums being 2. It does not matter what you leave beyond the returned k (hence they are underscores).
Example 2:
Input: nums = [0,1,2,2,3,0,4,2], val = 2
Output: 5, nums = [0,1,4,0,3,_,_,_]
Explanation: Your function should return k = 5, with the first five elements of nums containing 0, 0, 1, 3, and 4. Note that the five elements can be returned in any order. It does not matter what you leave beyond the returned k (hence they are underscores).
Constraints:
0 <= nums.length <= 100
0 <= nums[i] <= 50
0 <= val <= 100
Thought Process
The important point of this problem is The elements of an array are contiguous in memory addresses, and a specific element in the array cannot be deleted individually; it can only be overwritten.
Brute Force
The brute force solution to this problem is two nested for loops, where one for loop iterates through the array elements and the second for loop updates the array.
It is clear that the time complexity of the brute force solution is O(n^2), and this problem can pass the LeetCode with a brute force approach.
The code is as follows:
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
i, l = 0, len(nums)
while i < l:
if nums[i] == val: # Find nodes that equal the target value.
for j in range(i+1, l): # Remove the element and shift the subsequent elements forward.
nums[j - 1] = nums[j]
l -= 1
i -= 1
i += 1
return l
- Time Complexity: O(n^2)
- Space Complexity: O(1)
Two-pointer (Fast and Slow)
Two-pointer technique (fast and slow pointer method): It uses a fast pointer and a slow pointer to complete the work of two nested for loops within a single for loop.
Definition of Fast and Slow Pointers
• Fast Pointer: Used to find elements for the new array, which excludes the target element.
• Slow Pointer: Points to the position where the new array’s index is updated.
The two-pointer technique (fast and slow pointer method) is very common in operations involving arrays and linked lists. Many interview questions that test operations on arrays, linked lists, and strings utilize this approach.
The code is as follows:
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
fast = 0 # fast pointer
slow = 0 # slow pointer
size = len(nums)
while fast < size: # The equality is not included because when a == size, accessing nums[a] would result in an out-of-bounds (out of range) error.
# The slow pointer is used to collect values that are not equal to val. If the value at the fast pointer is not equal to val, it will be swapped with the value at the slow pointer.
if nums[fast] != val:
nums[slow] = nums[fast]
slow += 1
fast += 1
return slow
Note that these implementations do not change the relative order of the elements!
- Time Complexity: O(n)
- Space Complexity: O(1)
Two-pointer (Left and Right)
This new solution introduces a variation of the two-pointer technique, but instead of using fast and slow pointers moving in one direction, it uses left and right pointers that traverse the array from opposite ends. The key idea is to swap elements from the left and right sides when necessary to efficiently remove the target value from the array.
In this approach, two pointers are initialized:
• Left Pointer: Starts from the beginning of the array.
• Right Pointer: Starts from the end of the array.
The idea is to:
1. Move the left pointer forward until it finds an element equal to val.
2. Move the right pointer backward until it finds an element that is not equal to val.
3. Swap the elements at the left and right pointers if the left pointer is still less than the right pointer. After swapping, increment the left pointer and decrement the right pointer to continue the process.
4. Repeat the process until the left pointer surpasses the right pointer.
This ensures that all elements that are not equal to val are shifted towards the beginning of the array. The function returns the position of the left pointer, which indicates the number of elements that are not equal to val.
The code is as follows:
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
n = len(nums)
left, right = 0, n - 1
while left <= right:
while left <= right and nums[left] != val:
left += 1
while left <= right and nums[right] == val:
right -= 1
if left < right:
nums[left] = nums[right]
left += 1
right -= 1
return left
- Time Complexity: O(n)
- Space Complexity: O(1) Related Problems
Discussion