27. 移除元素(Remove Element)
频次 ★★ · 难度 🟢 · 高频:全厂
题目
给定一个数组和一个值 val,原地移除所有等于 val 的元素,返回新长度。
示例:
输入: nums = [3, 2, 2, 3], val = 3
输出: length = 2, nums 前两位为 [2, 2]
思路
快慢双指针:
fast遍历数组slow指向下一个非val元素应放的位置- 当
nums[fast] != val时,复制到slow位置
代码
public int removeElement(int[] nums, int val) {
int slow = 0;
for (int fast = 0; fast < nums.length; fast++) {
if (nums[fast] != val) {
nums[slow] = nums[fast];
slow++;
}
}
return slow;
}复杂度
- 时间:O(n) — 遍历一次
- 空间:O(1) — 只用两个指针
边界条件
- 空数组:循环不执行,返回 0。
- 数组中不存在
val:slow和fast始终同步递增,等价于原样返回。 - 数组全等于
val:slow始终为 0,返回 0。
变式
- 如果不要求保持相对顺序:可以用双指针从两端向中间夹逼,遇到
nums[left] == val就用nums[right]覆盖它并right--,减少不必要的搬移次数(当val很少时更快)。 - 结合 283. 移动零:本质是同一套”快慢指针原地过滤”模板,只是过滤条件从”等于 val”换成了”等于 0,且要把过滤掉的元素挪到末尾而不是丢弃”。
易错点
- 和 26. 删除有序数组中的重复项 容易混淆:这题数组不要求有序,判断条件是”是否等于 val”而不是”是否等于前一个元素”。
- 不需要把
slow之后的元素清零,返回的新长度已经说明了有效区间。
面试追问
- 双指针从两端夹逼 vs 从同向遍历,什么场景选哪种? 不要求保持顺序时优先选两端夹逼,因为可以省去大量元素搬移(尤其
val很多的时候);要求保持相对顺序时只能用同向快慢指针。 - 这题和”排序数组的原地去重”能不能用同一套代码? 不能直接复用,判断条件不同(等于目标值 vs 等于前一个元素),但整体”双指针原地压缩”的骨架是一样的,属于同一类模板题。
关联题
- 同套路:26. 删除有序数组中的重复项、283. 移动零 —— 快慢指针原地压缩
- 同套路:顺序可打乱时用首尾双指针,把待删元素换到末尾,写入次数更少