TL;DR
Two pointers (read/write compaction) β O(n) time, O(1) space.
Approach 1 β Brute force: repeated in-place deletion
The literal reading: find an occurrence of val, delete it (shifting everything after it left by one), repeat.
from typing import List
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
i = 0
while i < len(nums):
if nums[i] == val:
del nums[i] # shifts the tail left: O(n)
else:
i += 1
return len(nums)
Complexity: O(nΒ²) time worst case (an array full of val triggers n deletions, each shifting O(n) elements), O(1) space.
n β€ 100 makes this pass easily, but the interviewer wants the shift-free idea β and del on a Python list hides exactly the cost the problem is about.
Approach 2 β Two pointers: read/write compaction
The insight: you never need to delete anything β you only need the keepers packed at the front. Sweep a read pointer across the array and copy each keeper to a write pointer that only advances on keeps. Elements equal to val are simply never copied, so they end up overwritten or stranded past k. This is the standard two-pointer compaction (a.k.a. stable partition) that underlies Remove Duplicates, Move Zeroes, and friends.
from typing import List
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
w = 0
for r in range(len(nums)):
if nums[r] != val:
nums[w] = nums[r]
w += 1
return w
Walkthrough on nums = [0, 1, 2, 2, 3, 0, 4, 2], val = 2:
| r | nums[r] | action | w after | array front |
|---|
| 0 | 0 | keep β write at 0 | 1 | [0, β¦] |
| 1 | 1 | keep β write at 1 | 2 | [0, 1, β¦] |
| 2 | 2 | skip | 2 | β |
| 3 | 2 | skip | 2 | β |
| 4 | 3 | keep β write at 2 | 3 | [0, 1, 3, β¦] |
| 5 | 0 | keep β write at 3 | 4 | [0, 1, 3, 0, β¦] |
| 6 | 4 | keep β write at 4 | 5 | [0, 1, 3, 0, 4, β¦] |
| 7 | 2 | skip | 5 | β |
Return k = 5; the first five slots hold [0, 1, 3, 0, 4] β matches the expected output, order preserved as a bonus.
Complexity: O(n) time, O(1) space. Every element is read once and written at most once.
Approach 3 β Two pointers from both ends (few removals)
The insight: if val is rare, Approach 2 still rewrites nearly every element. Instead, when the left pointer hits an offender, overwrite it with the last element and shrink the arrayβs logical end β the number of writes equals the number of removals, not the number of keepers. Order is not preserved, which the problem explicitly allows.
from typing import List
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
i = 0
end = len(nums)
while i < end:
if nums[i] == val:
nums[i] = nums[end - 1]
end -= 1 # re-examine index i: the moved element may be val too
else:
i += 1
return end
Walkthrough on nums = [3, 2, 2, 3], val = 3:
i = 0: nums[0] is 3 β copy nums[3] (also 3) over it, end = 3. Array: [3, 2, 2, Β·].
i = 0 again: still 3 β copy nums[2] (2) over it, end = 2. Array: [2, 2, Β·, Β·].
i = 0: 2 β 3 β advance. i = 1: 2 β 3 β advance. i = 2 = end β stop.
- Return
k = 2; first two slots are [2, 2]. Matches the expected output.
Complexity: O(n) time, O(1) space β but only about (number of removals) writes, ideal when removals are rare.
Common pitfalls
- In Approach 3, incrementing
i after a swap: the element pulled from the back is unexamined and might itself be val (step 2 of the walkthrough is exactly that case).
- Returning the array instead of
k β the judge reads the count from the return value and only inspects nums up to it.
- Reaching for
nums.remove(val) in a loop or a list comprehension [x for x in nums if x != val] β the first is the hidden-shift brute force, the second allocates the forbidden second array (rebinding, not in-place).
- Empty input: both loops fall through naturally and return 0 β donβt special-case it.
Pattern takeaway
In-place filtering is a read/write two-pointer: the read pointer visits everything, the write pointer marks the boundary of βaccepted so farβ, and nothing is ever deleted β only overwritten. When order doesnβt matter and removals are rare, the swap-with-last variant trades stability for the minimum possible number of writes. These two compaction idioms cover the whole remove/dedupe/partition family.