赞
踩
常见的双指针有两种形式,一种是对撞指针,一种是左右指针。
双指针(Two Pointers):指的是在遍历元素的过程中,不是使用单个指针进行访问,而是使用两个指针进行访问,从而达到相应的目的。如果两个指针方向相反,则称为「对撞指针」。如果两个指针方向相同,则称为「快慢指针」。如果两个指针分别属于不同的数组 / 链表,则称为「分离双指针」。
在数组的区间问题上,暴力算法的时间复杂度往往是 O ( n 2 ) O(n^2) O(n2)。而双指针利用了区间「单调性」的性质,可以将时间复杂度降到 O ( n ) O(n) O(n)。
对撞指针:指的是两个指针
left
、right
分别指向序列第一个元素和最后一个元素,然后left
指针不断递增,right
不断递减,直到两个指针的值相撞(即left == right
),或者满足其他要求的特殊条件为止。
对撞指针一般用于顺序结构中,也称为左右指针。
left, right = 0, len(nums) - 1
while left < right:
if 满足要求的特殊条件:
return 符合条件的值
elif 一定条件 1:
left += 1
elif 一定条件 2:
right -= 1
return 没找到 或 找到对应值
对撞指针一般用来解决有序数组或者字符串问题:
下面我们根据具体例子来讲解如何使用对撞指针来解决问题。
给定 n
个非负整数 a1, a2, ..., an
,每个数代表坐标中的一个点 (i, ai)
。在坐标内画 n
条垂直线,垂直线 i
的两个端点分别为 (i, ai)
和 (i, 0)
。
找出其中的两条线,使得它们与 x
轴共同构成的容器可以容纳最多的水。
设两个指针 left
、right
分别指向容器的左右两个端点,此时容器的容积为:
v = (right - left) * min( height[right], height[left])
容器的左边界为 height[left]
,右边界为 height[right]
。
为了方便叙述,我们假设「左边边界」小于「右边边界」。
如果此时我们固定一个边界,改变另一个边界,水的容积会有如下变化形式:
由此可见,左边界和其余边界的组合情况都可以舍去。所以我们可以 left++
跳过这个边界,继续去判断下一个左右边界。
当我们不断重复上述过程,每次都可以舍去大量不必要的枚举过程,直到 left
与 right
相遇。期间产生的所有的容积里面的最大值,就是最终答案
class Solution {
public:
int maxArea(vector<int>& height) {
int left = 0, right = height.size() - 1, res = 0;
while(left < right) {
if(height[left]<height[right])
{
res=max(res,height[left]*(right-left));
left++;
}
else {
res=max(res,height[right]*(right-left));
right--;
}
}
return res;
}
};
快慢指针:指的是两个指针从同一侧开始遍历序列,且移动的步长一个快一个慢。移动快的指针被称为 「快指针(fast)」,移动慢的指针被称为「慢指针(slow)」。两个指针以不同速度、不同策略移动,直到快指针移动到数组尾端,或者两指针相交,或者满足其他特殊条件时为止。
快慢指针,又称为乌龟赛跑算法,其基本思想是使用两个移动速度不同的指针在数组或链表等序列结构上移动。
快慢指针是指两个指针在链表上移动的速度不同,常用于解决环形链表相关的问题。快指针每次移动两步,慢指针每次移动一步,因此快指针会比慢指针快一倍。快慢指针的经典应用包括:
slow
、fast
。slow
一般指向序列第一个元素,即:slow = 0
,fast
一般指向序列第二个元素,即:fast = 1
。slow += 1
。当满足另外一定条件时(也可能不需要满足条件),将快指针右移,即 fast += 1
。fast == len(nums) - 1
),或者两指针相交,或者满足其他特殊条件时跳出循环体。slow = 0
fast = 1
while 没有遍历完:
if 满足要求的特殊条件:
slow += 1
fast += 1
return 合适的值
快慢指针一般用于处理数组中的移动、删除元素问题,或者链表中的判断是否有环、长度问题。关于链表相关的双指针做法我们到链表章节再详细讲解。
下面我们根据具体例子来讲解如何使用快慢指针来解决问题。
26. 删除有序数组中的重复项 - 力扣(LeetCode)
描述:给定一个有序数组 nums
。
要求:删除数组 nums
中的重复元素,使每个元素只出现一次。并输出去除重复元素之后数组的长度。
说明:
示例:
输入:nums = [1,1,2]
输出:2, nums = [1,2,_]
解释:函数应该返回新的长度 2 ,并且原数组 nums 的前两个元素被修改为 1, 2 。不需要考虑数组中超出新长度后面的元素。
输入:nums = [0,0,1,1,1,2,2,3,3,4]
输出:5, nums = [0,1,2,3,4]
解释:函数应该返回新的长度 5 , 并且原数组 nums 的前五个元素被修改为 0, 1, 2, 3, 4 。不需要考虑数组中超出新长度后面的元素。
因为数组是有序的,那么重复的元素一定会相邻。
删除重复元素,实际上就是将不重复的元素移到数组左侧。考虑使用双指针。具体算法如下:
slow
,fast
。其中 slow
指向去除重复元素后的数组的末尾位置。fast
指向当前元素。slow
在后, fast
在前。令 slow = 0
,fast = 1
。slow
位置上元素值和 fast
位置上元素值是否相等。
slow
后移一位,将 fast
指向位置的元素复制到 slow
位置上。fast
右移 1
位。fast
等于数组长度。slow + 1
即为新数组长度。int removeDuplicates(vector<int>& nums) {
if(nums.empty()) return 0;
int p = 0;
int q = 1;
while(q < nums.size()){
if(nums[p] != nums[q]){
nums[p + 1] = nums[q];
p++;
}
q++;
}
return p + 1;
}
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。