198 lines
8.6 KiB
YAML
198 lines
8.6 KiB
YAML
title: Remove Element
|
|
slug: remove-element
|
|
difficulty: easy
|
|
leetcode_id: 27
|
|
leetcode_url: https://leetcode.com/problems/remove-element/
|
|
categories:
|
|
- arrays
|
|
- two-pointers
|
|
patterns:
|
|
- two-pointers
|
|
|
|
function_signature: "def remove_element(nums: list[int], val: int) -> int:"
|
|
|
|
test_cases:
|
|
visible:
|
|
- input: { nums: [3, 2, 2, 3], val: 3 }
|
|
expected: 2
|
|
- input: { nums: [0, 1, 2, 2, 3, 0, 4, 2], val: 2 }
|
|
expected: 5
|
|
- input: { nums: [1], val: 1 }
|
|
expected: 0
|
|
hidden:
|
|
- input: { nums: [], val: 0 }
|
|
expected: 0
|
|
- input: { nums: [1, 2, 3, 4, 5], val: 6 }
|
|
expected: 5
|
|
- input: { nums: [4, 4, 4, 4], val: 4 }
|
|
expected: 0
|
|
|
|
description: |
|
|
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 first `k` elements of `nums` contain the elements which are not equal to `val`. The remaining elements of `nums` are not important as well as the size of `nums`.
|
|
- Return `k`.
|
|
|
|
constraints: |
|
|
- `0 <= nums.length <= 100`
|
|
- `0 <= nums[i] <= 50`
|
|
- `0 <= val <= 100`
|
|
|
|
examples:
|
|
- 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)."
|
|
- 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)."
|
|
|
|
explanation:
|
|
intuition: |
|
|
Imagine you have a row of boxes, and you need to push all the unwanted boxes to the end while keeping the good boxes at the front.
|
|
|
|
The key insight is that we don't actually need to "delete" elements — we just need to **overwrite** them with good values and keep track of how many good values we have. Think of it like reorganising a bookshelf: instead of removing books you don't want, you simply shift the books you want to keep toward one end.
|
|
|
|
Since the problem allows the remaining elements to be in any order, we have flexibility in how we approach this. We can use a **write pointer** that tracks where the next "good" element should go. Every time we find an element that isn't `val`, we write it to the position indicated by our write pointer and advance the pointer.
|
|
|
|
This is the classic **two-pointer technique**: one pointer (`i`) scans through all elements, while another pointer (`k`) marks where to place elements we want to keep.
|
|
|
|
approach: |
|
|
We solve this using the **Two Pointers** technique:
|
|
|
|
**Step 1: Initialise the write pointer**
|
|
|
|
- `k`: Set to `0` — this tracks the position where the next non-`val` element should be placed
|
|
|
|
|
|
|
|
**Step 2: Iterate through the array**
|
|
|
|
- Use a read pointer `i` to scan each element from left to right
|
|
- If `nums[i] != val`, it's a "keeper" — copy it to position `k` and increment `k`
|
|
- If `nums[i] == val`, skip it (don't increment `k`)
|
|
|
|
|
|
|
|
**Step 3: Return the count**
|
|
|
|
- After processing all elements, `k` equals the number of elements that are not `val`
|
|
- The first `k` positions of `nums` now contain all the non-`val` elements
|
|
|
|
|
|
|
|
This works because `k` is always less than or equal to `i`. We're essentially compacting the array by overwriting positions with good values as we find them.
|
|
|
|
common_pitfalls:
|
|
- title: Creating a New Array
|
|
description: |
|
|
A common mistake is to create a new list and copy non-`val` elements into it:
|
|
|
|
```python
|
|
result = [x for x in nums if x != val]
|
|
```
|
|
|
|
While this produces the correct values, it uses **O(n) extra space** and doesn't modify `nums` in-place. The problem explicitly requires in-place modification.
|
|
wrong_approach: "Creating a new array with list comprehension"
|
|
correct_approach: "Modify nums in-place using two pointers"
|
|
|
|
- title: Using remove() or del in a Loop
|
|
description: |
|
|
Another tempting approach is to iterate and remove elements:
|
|
|
|
```python
|
|
for x in nums:
|
|
if x == val:
|
|
nums.remove(val)
|
|
```
|
|
|
|
This has two problems:
|
|
1. **Modifying a list while iterating** causes elements to be skipped
|
|
2. `remove()` is O(n) for each call, making the total complexity O(n²)
|
|
wrong_approach: "Using remove() or del while iterating"
|
|
correct_approach: "Use two pointers to avoid modification during iteration"
|
|
|
|
- title: Forgetting to Handle Empty Arrays
|
|
description: |
|
|
With `nums.length` potentially being `0`, ensure your solution handles empty arrays gracefully. The two-pointer approach naturally handles this — the loop simply doesn't execute, and we return `k = 0`.
|
|
|
|
key_takeaways:
|
|
- "**Two-pointer compaction**: Use a slow 'write' pointer and a fast 'read' pointer to compact elements in-place"
|
|
- "**In-place modification**: When order doesn't matter, overwriting is simpler than shifting or swapping"
|
|
- "**Foundation for similar problems**: This pattern applies to Remove Duplicates from Sorted Array, Move Zeroes, and other array compaction problems"
|
|
- "**O(1) space discipline**: In-place algorithms are essential when memory is constrained or when the problem explicitly requires it"
|
|
|
|
time_complexity: "O(n). We traverse the array once with the read pointer, performing O(1) work at each step."
|
|
space_complexity: "O(1). We only use a single integer variable `k` regardless of input size."
|
|
|
|
solutions:
|
|
- approach_name: Two Pointers
|
|
is_optimal: true
|
|
code: |
|
|
def remove_element(nums: list[int], val: int) -> int:
|
|
# k tracks where to place the next non-val element
|
|
k = 0
|
|
|
|
# Scan through every element
|
|
for i in range(len(nums)):
|
|
# If current element is not the value to remove
|
|
if nums[i] != val:
|
|
# Place it at position k and advance k
|
|
nums[k] = nums[i]
|
|
k += 1
|
|
|
|
# k is now the count of elements that are not val
|
|
return k
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Single pass through the array.
|
|
|
|
**Space Complexity:** O(1) — Only one variable `k` used.
|
|
|
|
The read pointer `i` examines each element. When we find a non-`val` element, we copy it to position `k` and increment `k`. Since `k <= i` always, we never overwrite an element we haven't yet examined.
|
|
|
|
- approach_name: Two Pointers (Swap from End)
|
|
is_optimal: true
|
|
code: |
|
|
def remove_element(nums: list[int], val: int) -> int:
|
|
# Two pointers: left starts at beginning, right at end
|
|
left = 0
|
|
right = len(nums) - 1
|
|
|
|
while left <= right:
|
|
if nums[left] == val:
|
|
# Swap with element from the end
|
|
nums[left] = nums[right]
|
|
right -= 1
|
|
# Don't increment left - need to check swapped element
|
|
else:
|
|
left += 1
|
|
|
|
# left is now the count of non-val elements
|
|
return left
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Each element is examined at most once.
|
|
|
|
**Space Complexity:** O(1) — Only two pointers used.
|
|
|
|
This variant is more efficient when `val` is rare. Instead of copying every non-`val` element, we only swap when we encounter `val`. We replace it with an element from the end and shrink the valid range. This minimises the number of writes.
|
|
|
|
- approach_name: Brute Force (Extra Space)
|
|
is_optimal: false
|
|
code: |
|
|
def remove_element(nums: list[int], val: int) -> int:
|
|
# Create a list of elements to keep
|
|
keep = [x for x in nums if x != val]
|
|
|
|
# Copy back to original array
|
|
for i, x in enumerate(keep):
|
|
nums[i] = x
|
|
|
|
return len(keep)
|
|
explanation: |
|
|
**Time Complexity:** O(n) — Two passes through the array.
|
|
|
|
**Space Complexity:** O(n) — Extra list to store non-val elements.
|
|
|
|
This approach works but violates the in-place requirement. It uses O(n) extra space for the temporary list. Included to illustrate why the two-pointer approach is preferred when in-place modification is required.
|