27.移除元素

给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。

不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。

元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。

示例 1:

1
2
3
输入:nums = [3,2,2,3], val = 3
输出:2, nums = [2,2,_,_]
解释:函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。你不需要考虑数组中超出新长度后面的元素。例如,函数返回的新长度为 2 ,而 nums = [2,2,3,3] 或 nums = [2,2,0,0],也会被视作正确答案。

示例 2:

1
2
3
输入:nums = [0,1,2,2,3,0,4,2], val = 2
输出:5, nums = [0,1,3,0,4,_,_,_]
解释:函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。注意这五个元素可为任意顺序。你不需要考虑数组中超出新长度后面的元素。

解题思路

这是一道经典的双指针题目,与删除重复项类似,但处理的是指定值的移除。

核心思想

  • 使用双指针,一个指针指向当前要填充的位置
  • 另一个指针遍历数组,跳过要移除的元素
  • 将不等于val的元素放到前面

方法一:双指针解法(推荐)

时间复杂度 O(n)、空间复杂度 O(1)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
/**
* @param {number[]} nums
* @param {number} val
* @return {number}
*/
var removeElement = function(nums, val) {
let slow = 0; // 指向当前要填充的位置

for (let fast = 0; fast < nums.length; fast++) {
// 如果当前元素不等于val,将其放到slow位置
if (nums[fast] !== val) {
nums[slow] = nums[fast];
slow++;
}
}

return slow;
};

方法二:优化版本

1
2
3
4
5
6
7
8
9
10
11
12
var removeElement = function(nums, val) {
let i = 0;

for (let j = 0; j < nums.length; j++) {
if (nums[j] !== val) {
nums[i] = nums[j];
i++;
}
}

return i;
};

方法三:交换法(保持相对顺序)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
var removeElement = function(nums, val) {
let left = 0;
let right = nums.length - 1;

while (left <= right) {
// 从左边找到等于val的元素
while (left <= right && nums[left] !== val) {
left++;
}

// 从右边找到不等于val的元素
while (left <= right && nums[right] === val) {
right--;
}

// 交换元素
if (left < right) {
[nums[left], nums[right]] = [nums[right], nums[left]];
}
}

return left;
};

方法四:使用filter(不符合要求,但思路清晰)

1
2
3
4
5
6
7
8
9
10
var removeElement = function(nums, val) {
const filtered = nums.filter(num => num !== val);

// 将结果复制回原数组
for (let i = 0; i < filtered.length; i++) {
nums[i] = filtered[i];
}

return filtered.length;
};

解题要点

  1. 原地操作:不能使用额外空间,必须原地修改数组
  2. 双指针技巧:使用快慢指针,快指针遍历,慢指针记录位置
  3. 边界处理:空数组和所有元素都等于val的情况
  4. 顺序问题:题目说明顺序可以改变

面试要点

  • 高频考点:这道题是面试中的高频题目
  • 原地操作:考察对数组原地操作的理解
  • 双指针应用:双指针的经典应用场景
  • 空间复杂度:要求O(1)空间复杂度

相关题目

扩展思考

  1. 为什么使用双指针?

    • 快指针遍历数组寻找不等于val的元素
    • 慢指针记录当前要填充的位置
  2. 时间复杂度分析

    • 只需要遍历一次数组:O(n)
    • 空间复杂度:O(1)
  3. 边界情况

    • 空数组:返回0
    • 所有元素都等于val:返回0
    • 没有元素等于val:返回原数组长度
  4. 算法正确性

    • 保证移除所有等于val的元素
    • 原地修改数组
    • 返回正确的长度
  5. 与删除重复项的区别

    • 删除重复项:比较相邻元素
    • 移除元素:与指定值比较

最后更新: 2021-09-07