questions M-R

This commit is contained in:
2025-05-25 12:43:25 +01:00
parent 5dbe52df0d
commit ddceeec07e
62 changed files with 12841 additions and 0 deletions

View File

@@ -0,0 +1,179 @@
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
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
&nbsp;
**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`)
&nbsp;
**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
&nbsp;
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.