1.使用场景:
一般在需要取数组某一段元素时,可以快慢指针并行
双指针法(快慢指针法)在数组和链表的操作中是非常常见的,很多考察数组、链表、字符串等操作的面试题,都使用双指针法。
2.类型:
快慢指针,头指针与尾指针,滑动窗口 等
3.使用方法
(1)for循环里面的j就相当于一个指针
然后可以定义一个全局变量i作为另一个指针,在for里面用i++移动指针
let i = 0
for (let j = 0; j < arr.length; j++){
arr[i++] = arr[j]
}
(2)有时是for循环里面直接定义两个指针
for (let i = 1, j = 0; arr[i] < arr[j]){
}
4.应用场景
(1)数组移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并原地修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
思路:快指针寻找新数组的元素(不等于val的元素)
慢指针标识新数组当前能够添加新元素的位置(实际上是覆盖旧数组的元素)
// 时间复杂度:O(n)
// 空间复杂度:O(1)
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = 0;
for (int fastIndex = 0; fastIndex < nums.size(); fastIndex++) { //一开始两个指针都指向一个元素
if (val != nums[fastIndex]) { //一旦等于,快指针就把慢指针甩开了
nums[slowIndex++] = nums[fastIndex]; //一开始相当于自己给自己赋值
//一旦甩开了就是后一个给前一个赋值
}
}
return slowIndex;
}
};
(2)有序数组的平方(非递减顺序的数组,有正有负)
给你一个按 非递减顺序 排序的整数数组 nums,返回 每个数字的平方 组成的新数组,要求也按 非递减顺序 排序。
思路:一个头指针,一个尾指针
比较两者当前所指元素的平方的值,放入结果集中
class Solution {
public:
vector<int> sortedSquares(vector<int>& A) {
int k = A.size() - 1; //指向新数组,从尾部开始移动
vector<int> result(A.size(), 0); //新数组
for (int i = 0, j = A.size() - 1; i <= j;) { // 注意这里要i <= j,因为最后要处理两个元素
if (A[i] * A[i] < A[j] * A[j]) {
result[k--] = A[j] * A[j];
j--;
}
else {
result[k--] = A[i] * A[i];
i++;
}
}
return result;
}
};
(3)长度最小的子数组
给定一个含有 n 个正整数的数组和一个正整数 s ,找出该数组中满足其和 ≥ s 的长度最小的 连续 子数组,并返回其长度。如果不存在符合条件的子数组,返回 0。
思路:滑动窗口也可以理解为双指针法的一种!
class Solution {
public:
int minSubArrayLen(int s, vector<int>& nums) {
int result = INT32_MAX;
int sum = 0; // 滑动窗口数值之和
int i = 0; // 滑动窗口起始位置
int subLength = 0; // 滑动窗口的长度
for (int j = 0; j < nums.size(); j++) {
sum += nums[j];
// 注意这里使用while,每次更新 i(起始位置),并不断比较子序列是否符合条件
while (sum >= s) { //一旦和超过了目标值
subLength = (j - i + 1); // 取子序列的长度
result = result < subLength ? result : subLength; //如果出现了更短的,就把结果改成更短的
sum -= nums[i++]; // 移除第一个元素。这里体现出滑动窗口的精髓之处,不断变更i(子序列的起始位置)
}
}
// 如果result没有被赋值的话,就返回0,说明没有符合条件的子序列
return result == INT32_MAX ? 0 : result;
}
};