第三十五天:移除元素
移除元素:原地操作的双指针技巧
在处理数组相关题目时,"原地修改"是一个关键要求——这意味着我们必须在原数组上直接操作,而不能使用额外存储空间。
一、题目
给定一个数组 nums
和一个目标值 val
,需要原地移除所有等于 val
的元素,然后返回“与 val
不同的元素的数量 k
”。
额外要求:
- 数组的前
k
个元素必须是“不等于val
的有效元素”(顺序可改变)。 - 无需关心数组中
k
之后的元素。
二、核心思路:双指针(快慢指针)
我们可以用两个“逻辑角色”的指针来完成操作:
- 慢指针
k
:表示「下一个要放置**有效元素(不等于val
)**的位置」,同时也记录「当前已保留的有效元素个数」。 - 快指针
i
:遍历整个数组,寻找「不等于val
的元素」。
三、代码实现
class Solution {
public:int removeElement(vector<int>& nums, int val) {int k = 0; // 慢指针:记录有效元素个数 + 下一个有效元素的放置位置for (int i = 0; i < nums.size(); ++i) {if (nums[i] != val) { // 遇到有效元素(不等于 val)nums[k] = nums[i]; // 放到数组的前 k 位k++; // 有效元素数量 +1,更新下一个放置位置}}return k; // 返回有效元素的数量}
};
四、代码解析
- 初始化:设置
k = 0
,表示当前有效元素个数为 0,下一个有效元素应放置在索引 0 的位置。 - 遍历数组:使用快指针
i
从数组首元素开始遍历。 - 元素处理:
- 当
nums[i] != val
(遇到有效元素):- 将
nums[i]
赋值到nums[k]
位置(维护有效元素的前k
位) - 递增
k
(有效元素计数增加,同时更新下一个存放位置)
- 将
- 当
nums[i] == val
(遇到待移除元素):跳过处理,保持k
不变
- 当
- 结果返回:最终
k
值即为数组中不等于val
的元素数量,且数组前k
个元素即为所有有效元素。
五、示例验证
以示例 1 为例,详细说明移除元素的过程:
输入数组:nums = [3,2,2,3]
,需要移除的值 val = 3
完整遍历过程如下:
-
初始化阶段:
- 设定慢指针
k = 0
(表示有效元素的位置) - 快指针
i
从 0 开始遍历整个数组
- 设定慢指针
-
遍历过程:
-
第一轮循环 (
i=0
):- 检查
nums[0] = 3
- 与目标值
val=3
相等 → 需要移除 - 保持
k=0
,不进行赋值操作 - 当前数组状态:
[3,2,2,3]
(未改变)
- 检查
-
第二轮循环 (
i=1
):- 检查
nums[1] = 2
- 与目标值不相等 → 需要保留
- 将
nums[1]
的值赋给nums[k]
(即nums[0] = 2
) - 递增
k=1
- 数组变为:
[2,2,2,3]
- 检查
-
第三轮循环 (
i=2
):- 检查
nums[2] = 2
- 与目标值不相等 → 需要保留
- 将
nums[2]
的值赋给nums[k]
(即nums[1] = 2
) - 递增
k=2
- 数组变为:
[2,2,2,3]
- 检查
-
第四轮循环 (
i=3
):- 检查
nums[3] = 3
- 与目标值相等 → 需要移除
- 保持
k=2
,不进行赋值操作 - 数组保持:
[2,2,2,3]
- 检查
-
-
最终结果:
- 返回的有效元素个数
k = 2
- 数组前 2 个元素为
[2,2]
- 虽然数组后面还有元素,但根据题目要求,只需保证前 k 个元素正确即可
- 返回的有效元素个数
这个示例展示了如何通过双指针(快慢指针)在原地修改数组,同时保持其他元素的相对顺序。注意最终数组的后两个元素 [2,3]
虽然还存在,但不在有效范围内,可以忽略。
六、复杂度分析
- 时间复杂度:O(n)O(n)O(n)。仅需单次遍历数组,其中
n
表示数组长度。 - 空间复杂度:O(1)O(1)O(1)。仅使用固定数量的变量(
k
和i
),无需额外存储空间,实现真正的原地操作。