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。
  • 数组中不存在 valslowfast 始终同步递增,等价于原样返回。
  • 数组全等于 valslow 始终为 0,返回 0。

变式

  • 如果不要求保持相对顺序:可以用双指针从两端向中间夹逼,遇到 nums[left] == val 就用 nums[right] 覆盖它并 right--,减少不必要的搬移次数(当 val 很少时更快)。
  • 结合 283. 移动零:本质是同一套”快慢指针原地过滤”模板,只是过滤条件从”等于 val”换成了”等于 0,且要把过滤掉的元素挪到末尾而不是丢弃”。

易错点

  • 26. 删除有序数组中的重复项 容易混淆:这题数组不要求有序,判断条件是”是否等于 val”而不是”是否等于前一个元素”。
  • 不需要把 slow 之后的元素清零,返回的新长度已经说明了有效区间。

面试追问

  • 双指针从两端夹逼 vs 从同向遍历,什么场景选哪种? 不要求保持顺序时优先选两端夹逼,因为可以省去大量元素搬移(尤其 val 很多的时候);要求保持相对顺序时只能用同向快慢指针。
  • 这题和”排序数组的原地去重”能不能用同一套代码? 不能直接复用,判断条件不同(等于目标值 vs 等于前一个元素),但整体”双指针原地压缩”的骨架是一样的,属于同一类模板题。

关联题