思路
经典的双指针技术,这里称为快慢指针。其中,r
(快指针)遍历数组,l
(慢指针)跟踪新数组的填充位置。这种方法有效地在原数组上进行操作,避免了使用额外空间。
代码步骤
-
初始化:
l
指针初始化为0
,用于指示新数组的当前填充位置。 -
遍历数组:
- 使用
r
指针从0
开始遍历数组nums
。 - 在每次迭代中,检查
nums[r]
是否等于val
。 - 如果
nums[r]
不等于val
,则将nums[r]
的值赋给nums[l]
,并将l
指针向前移动一位。
- 使用
-
返回结果:循环结束后,
l
指针的位置表示了新数组的长度,因此直接返回l
。
示例运行
假设 nums = [3, 2, 2, 3]
,val = 3
:
- 初始状态:
l = 0
,r = 0
r = 0
,nums[r] = 3
,与val
相等,什么都不做,只增加r
。r = 1
,nums[r] = 2
,与val
不等,将nums[r]
赋值给nums[l]
,nums[0] = 2
,l
变为 1。r = 2
,同上,nums[1] = 2
,l
变为 2。r = 3
,nums[r] = 3
,与val
相等,继续。- 结束遍历,返回
l = 2
。
这种方法不仅高效,还操作简单,利用双指针技术避免了不必要的内存使用,并且能够直接在输入数组上进行修改,符合题目要求。这是解决类似问题的一种非常实用的技术,特别适合在需要避免使用额外空间的场景中使用。
复杂度
-
时间复杂度:O(n),其中 n 是数组
nums
的长度。这是因为我们需要遍历整个数组一次,来检查每个元素是否需要被移除。 -
空间复杂度:O(1),我们没有使用额外的数组或数据结构,所有操作都在原数组上完成。
Code
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int l = 0;
for(int r = 0;r < nums.size();++ r){
if(nums[r] != val)
nums[l ++ ] = nums[r];
}
return l;
}
};
拓展
主要类型的双指针算法:
- 对撞指针:
适用场景:常用于有序数组或链表,用来查找两个元素的特定关系(如和为固定值)。
工作方式:分别在数据结构的起始和结束位置放置两个指针,根据比较结果向中间移动。例如,在有序数组中查找两数之和为特定值的问题,如果当前两指针指向值的和小于目标值,则移动左指针增大和;如果大于目标值,则移动右指针减小和。
示例问题:两数之和、三数之和。
- 快慢指针:
适用场景:主要用于处理循环或重复的问题,如检测链表中的循环。
工作方式:使用两个不同速度的指针(一个快指针和一个慢指针)。快指针每次移动两步,慢指针每次移动一步。如果存在循环,快慢指针最终会相遇。
示例问题:判断链表是否有环、寻找链表环的入口。
- 滑动窗口(可视为一种双指针技术):
适用场景:用于数组或字符串中,寻找满足特定条件的连续子区间。
工作方式:维护一个窗口的左右边界,根据当前窗口内的数据情况决定扩大还是缩小窗口,直至找到所有满足条件的窗口。
示例问题:最小覆盖子串、长度最小的子数组。