这道题和 LeetCode 26(删除有序数组中的重复项)是一对姐妹题,核心都是"原地修改数组"。有意思的是,这道题的双指针有两种完全不同的玩法——一个从前往后,一个两头往中间。两种思路都值得掌握,因为它们代表了双指针最经典的两种模式。
题目长什么样
给你一个数组 nums 和一个值 val,你需要原地移除所有数值等于 val 的元素。元素的顺序可能发生改变。然后返回 nums 中与 val 不同的元素的数量。
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,4,0,3,_,_,_]
说人话就是:把数组里所有等于 val 的元素删掉,把剩下的元素挤到数组前面,返回剩下几个。后面留了啥无所谓,评测机不看。
第一反应:快慢指针,一个读一个写
最直观的想法——用一个指针 i 扫描整个数组,另一个指针 k 记录"有效区域的末尾"。遇到不是 val 的元素就写入 k 的位置,然后 k 往前推一步。
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
k = 0
for i in range(len(nums)):
if nums[i] != val:
nums[k] = nums[i]
k += 1
return k
跑一遍示例 2 看看过程:
nums = [0, 1, 2, 2, 3, 0, 4, 2], val = 2
i=0 k=0
i=0: nums[0]=0 ≠ 2 → nums[0]=0, k=1
i=1: nums[1]=1 ≠ 2 → nums[1]=1, k=2
i=2: nums[2]=2 = 2 → 跳过
i=3: nums[3]=2 = 2 → 跳过
i=4: nums[4]=3 ≠ 2 → nums[2]=3, k=3
i=5: nums[5]=0 ≠ 2 → nums[3]=0, k=4
i=6: nums[6]=4 ≠ 2 → nums[4]=4, k=5
i=7: nums[7]=2 = 2 → 跳过
结果: nums = [0,1,3,0,4,_,_,_], k = 5
| 维度 | 值 | 说明 |
|---|---|---|
| 时间 | O(n) | 每个元素只看一次 |
| 空间 | O(1) | 只用了两个变量 |
这就是最标准的解法。代码简洁,思路清晰,面试写这个完全够用。
但仔细想想——当要删除的元素很少时,比如数组有 1000 个元素,只有最后 1 个等于 val,这个方法还是会把前面 999 个元素都复制一遍(虽然是复制到自己的位置)。能不能更聪明一点?
换个思路:左右指针,遇到了就换掉
如果题目说"顺序可以变"——那事情就好办了。等于 val 的元素我们根本不需要保留,直接从数组末尾拿一个元素来覆盖它就行。
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
if nums[left] == val:
nums[left] = nums[right]
right -= 1
else:
left += 1
return left
跑一遍示例 1 看看:
nums = [3, 2, 2, 3], val = 3
↑left ↑right
Step 1: nums[0]=3 = 3 → 用 nums[3]=3 覆盖 → [3,2,2,3], right=2
Step 2: nums[0]=3 = 3 → 用 nums[2]=2 覆盖 → [2,2,2,3], right=1
Step 3: nums[0]=2 ≠ 3 → left=1
Step 4: nums[1]=2 ≠ 3 → left=2
Step 5: left=2 > right=1 → 结束
结果: k=2, nums前2个 = [2,2]
再跑一遍示例 2:
nums = [0,1,2,2,3,0,4,2], val = 2
↑left ↑right
Step 1: nums[0]=0 ≠ 2 → left=1
Step 2: nums[1]=1 ≠ 2 → left=2
Step 3: nums[2]=2 = 2 → 用 nums[7]=2 覆盖 → [0,1,2,2,3,0,4,2], right=6
Step 4: nums[2]=2 = 2 → 用 nums[6]=4 覆盖 → [0,1,4,2,3,0,4,2], right=5
Step 5: nums[2]=4 ≠ 2 → left=3
Step 6: nums[3]=2 = 2 → 用 nums[5]=0 覆盖 → [0,1,4,0,3,0,4,2], right=4
Step 7: nums[3]=0 ≠ 2 → left=4
Step 8: nums[4]=3 ≠ 2 → left=5
Step 9: left=5 > right=4 → 结束
结果: k=5, nums前5个 = [0,1,4,0,3]
| 维度 | 值 | 说明 |
|---|---|---|
| 时间 | O(n) | 每个元素最多访问一次 |
| 空间 | O(1) | 只用了两个变量 |
| 赋值次数 | 最多 n 次 | 比快慢指针最多 2n 次更少 |
两种解法放在一起看
| 解法 | 时间 | 空间 | 赋值次数 | 保持顺序 |
|---|---|---|---|---|
| 快慢指针 | O(n) | O(1) | 最多 2n | ✅ 保持 |
| 左右双指针 | O(n) | O(1) | 最多 n | ❌ 不保证 |
- 快慢指针:万能解法,适用于所有"原地移除/过滤"类问题,而且保持元素原始顺序。
- 左右双指针:当
val很少时优势明显(赋值次数更少),但会打乱顺序。
面试时先写快慢指针,然后主动提"如果顺序不重要,还可以用左右指针减少赋值次数"——这种递进思考就是面试官想看到的。
这道题教会我什么
同一个技巧,不同的打开方式
双指针不是一种固定写法,而是一种思想——用两个标记位置来解决问题。快慢指针是"一个读一个写",左右指针是"两头往中间收敛"。两种模式在很多题目里都能见到:
- 快慢指针:删除重复元素、移动零、滑动窗口
- 左右指针:两数之和(有序数组)、盛水容器、回文判断
"顺序可以变"不是废话
题目特意说"元素的顺序可能发生改变",这个条件不是白给的——它直接打开了左右指针的解法。和 LeetCode 88 一样,题目里的每一个条件都值得多想一步。
O(n) 也能继续优化
两种解法时间复杂度都是 O(n),但实际赋值次数差了一倍。面试中如果能说出"虽然都是 O(n),但赋值操作次数不同",说明你对复杂度的理解已经不局限于大 O 符号了。
完整测试代码
from typing import List
class Solution:
def removeElement(self, nums: List[int], val: int) -> int:
k = 0
for i in range(len(nums)):
if nums[i] != val:
nums[k] = nums[i]
k += 1
return k
class Solution2:
def removeElement(self, nums: List[int], val: int) -> int:
left, right = 0, len(nums) - 1
while left <= right:
if nums[left] == val:
nums[left] = nums[right]
right -= 1
else:
left += 1
return left
if __name__ == "__main__":
s1 = Solution()
s2 = Solution2()
nums = [3, 2, 2, 3]
k = s1.removeElement(nums, 3)
print(f"解法一: k={k}, nums={nums[:k]}")
nums = [0, 1, 2, 2, 3, 0, 4, 2]
k = s1.removeElement(nums, 2)
print(f"解法一: k={k}, nums={nums[:k]}")
nums = [3, 2, 2, 3]
k = s2.removeElement(nums, 3)
print(f"解法二: k={k}, nums={nums[:k]}")
nums = [0, 1, 2, 2, 3, 0, 4, 2]
k = s2.removeElement(nums, 2)
print(f"解法二: k={k}, nums={nums[:k]}")
nums = []
k = s1.removeElement(nums, 0)
print(f"解法一: k={k}, nums={nums[:k]}")
nums = [1]
k = s1.removeElement(nums, 1)
print(f"解法一: k={k}, nums={nums[:k]}")
相关题目推荐:
- LeetCode 26 · 删除有序数组中的重复项(快慢指针的经典应用,顺序敏感)
- LeetCode 283 · 移动零(快慢指针的变体,把零移到末尾)
- LeetCode 80 · 删除有序数组中的重复项 II(LeetCode 26 的进阶版,允许重复两次)

176

被折叠的 条评论
为什么被折叠?



