一、原理
使用两个移动速度不相同的指针(快指针fastIndex和慢指针slowIndex)对数组、字符串、链表等线性表进行进行查找、增加、删除和排序等操作,其中快指针往往用于遍历原数组,慢指针往往用于标记新数组以便进行修改等操作,快慢指针法避免了对数组进行两重遍历,将时间复杂度由O(n*n)减为O(n)。
二、 限制条件
操作的数据类型为数组、字符串或链表。由于使用条件较少,快慢指针法在解决这些类型的题目中有广泛的应用。
三、时空复杂度
- 时间复杂度:O(n)
- 空间复杂度:O(1)
四、常见题型
1.删除有序数组中的重复项
题目出处:26. 删除有序数组中的重复项 - 力扣(LeetCode)
解题思路:本题中要求删除一个有序数组中所有的重复项并返回修改后数组的长度。同时注意到本题中函数使用传地址引用,直接在原数组中进行操作无需建立新的数组,故可以使用快慢指针法解决。
- 设置一个慢指针(slowIndex)用于指向新数组末位的后一位,用于在新数组中增加新元素
- 设置一个快指针(fastIndex)用于遍历整个数组
- 每当快指针在遍历时检测到元素的值发生一次变化,就将检测到的新的值添加到新数组的末尾,当原数组中一共有n个不同的元素时,fastIndex将在遍历的过程中检测到n次元素值变化,并将这n个元素存入新的数组中
代码实例:
class Solution {
public:
int removeDuplicates(vector<int>& nums) {
int slowIndex = 1; //具有双重含义:既是慢指针,也是新数组的长度
for(int fastIndex = 1; fastIndex < nums.size(); ++fastIndex) {
if(nums[fastIndex] != nums[fastIndex-1]) {
//当检测到元素数值变化的时候
nums[slowIndex] = nums[fastIndex];
++slowIndex;
}
}
return slowIndex;
}
};
注意事项:快指针初始值为1可以避免使用如下语句
if(nums[fastIndex] != nums[slowIndex+1])
在该语句中,我们使用了slowIndex+1有数据异常的风险。若提交,新数组末尾将出现错误结果-1094795586 ,这是一个 int32_t
类型数值的打印结果, 对应的十六进制是 0xBEBEBEBE
, 是 malloc 的内存没有初始化时按字节填充 0xBE
得到的。
2.移除指定元素
解题思路:本题给出一个特定的值val,要求删除数组中所有值等于val的元素,注意到本题使用传地址引用的方式调用数组,操作需要在原数组上完成,可以使用快慢指针思想。
- 创建一个慢指针(slowIndex),指向新建数组的末位元素,新数组的长度即为slowIndex+1
- 创建一个快指针(fastIndex),遍历整个数组
- 若发现值与val相同的元素,快指针将跳过该元素;若发现值与val不同的元素,则将其存储至新数组的末尾(慢指针所指位置的下一位)
代码实例:
class Solution {
public:
int removeElement(vector<int>& nums, int val) {
int slowIndex = -1; // 注意若设慢指针初值0,当nums[0] = val将会无法删除首个元素
int len = nums.size();
for(int fastIndex = 0; fastIndex < len; ++fastIndex) {
if(nums[fastIndex] != val) {
nums[++slowIndex] = nums[fastIndex];
}
}
return ++slowIndex;
}
};
注意事项:
- 慢指针初值需设为-1以便对原数组中的首个元素进行检查和删除操作。因为后续的操作相当于不断向新数组中添加新元素。若将慢指针初值设为0则相当于将首个节点直接纳入新数组中,当nums[0]=val时将无法删除nums[0]
- 注意nums[++slowIndex]既不等于nums[slowIndex++]也不等于nums[slowIndex+1]
int nums[] = {0, 1, 2, 3}; int i = 0; int m = nums[i+1]; // m = 1,i = 0 int m = nums[i++]; // m = 0,i = 1 int m = nums[++i]; // m = 1,i = 1