双指针
概念
相信很多人都对双指针算法有映像,在力扣题目中,很多类型的题目用双指针算法都能够被很好地解决。那么什么叫做双指针算法呢?其实就是在遍历某个对象时,不单单的使用一个指针来进行访问,而是再利用一个指针逆序或者正序的,并且结合另一个指针来进行问题的探讨。两个指针需要相互结合,这就形成了双指针算法
当然这是不具体的说法,真正具体的说法应该双指针算法基本应用在数组身上,并且双指针一般为数组的下标索引。在数组中解决各种“疑难杂症”,例如删除数组的重复项、三数之和、验证回文串以及接雨水诸多问题上,双指针算法都有“妙手回春”的功效。
接下来我们直接看“病”
例题
开胃小菜
首先我们写感受一下双指针,来一道比较简单的题目: 移动零
给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。
示例:
输入: [0,1,0,3,12]
输出: [1,3,12,0,0]
题目的很简短,我们呢,我就根据它的实例来分析分析
题目基本思想解析
这道题目的目的非常的明显,关键信息就在于nums[i] = 0这个条件,然后作出相应的判断,将其移动至末尾。那么怎么移动呢?这个时候就需要回想一下我们的双指针。我们定义两个指针i和j,也就是该数组的下标索引,判断当前第一个指针i所对应的num[i]是否等于0,如果等于0,那么下标索引i++; 如果不等于0,则将nums[i]的值赋值给nums[j],此时两个指针都需要进行自增操作。直到i遍历到数组最后一位,则表示数组当前已经将不为0的元素都赋值给了j前面的元素,这个时候再将j索引之后的元素全部赋值为0,就实现了0的转移。具体过程,如下图所示
图解
具体代码
class Solution {
public void moveZeroes(int[] nums) {
if (nums == null || nums.length <= 1)
return;
int j = 0;
for (int i = 0; i < nums.length; i++) {
if (nums[i] != 0) {
nums[j] = nums[i];
if (i != j)//代码进阶,用0覆盖已经赋值过给前面的值。
nums[i] = 0;
j++;
}
}
}
}
代码分析
这里呢也需要考虑特殊情况,当数组长度为1的时候,这个时候返回数组就行了,不管是0还是其他的元素,执行return操作即可。这里呢,我也进阶了一下,意思就是,若是前面的数组元素都不为0,那么此刻i = j,也就是不会执行赋值为零的操作,否则,就将已经赋值过给前面的值,用0覆盖。当然,这一步与主要思想没啥关系,只是省去了后面的赋值操作。这个题目也只是比较简单的双指针,接下来才是重头戏。
重头戏
再看稍微复杂一点的三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有和为 0 且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例 1:
输入: nums = [-1,0,1,2,-1,-4]
输出: [[-1,-1,2],[-1,0,1]]
思想解析
这里呢,其实可以用哈希表的做法,a + b + c = 0 ,那么代表a + b = -c,知道了-c的值,我就寻找哪两个数的和等于-c,再分别寻找这两个数,返回下标就好。不过这里我们采用双指针的做法。
寻找三个数字,主题思想: 我们首先要对nums进行排序,之后按照正序的遍历顺序,定义left和right两个指针,以i为第一个数字,left为第二个数字,赋值为i+1,right为第三个数字,赋值为size - 1,也就是最大的数字。若是三数之和大于零,说明数字过大,这个时候我们移动right指针,让和变小;若是三数之和小于零,这个时候我们移动left指针,让和变大;否则,就是三数之和等于零的情况,这个时候我们将这三个数push进结果数组就行。我们也是按照实例,来分析一下具体思路。
图解
具体代码
class Solution {
public List<List<Integer>> threeSum(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
Arrays.sort(nums);//排序
for (int i = 0; i < nums.length; i++) {
if (nums[i] > 0) {
return result;
}//排序之后若第一个数大于零,不可能凑成三元组,返回结果
if (i > 0 && nums[i] == nums[i - 1]) {
continue;
}//去重 两数相同
int left = i + 1;
int right = nums.length - 1;//左指针 右指针
while (right > left) {
int sum = nums[i] + nums[left] + nums[right];
//这个数,后一个数与最后一个数相加,结果保存在sum里
if (sum > 0) {
right--; //结果大于零则需要右指针左移
} else if (sum < 0) {
left++; //结果小零则需要左指针右移
} //否则就是sum=0,返回结果
else {
result.add(Arrays.asList(nums[i], nums[left], nums[right]));
while (right > left && nums[right] == nums[right - 1]) right--;
while (right > left && nums[left] == nums[left + 1]) left++;
//去重,碰到相同的数跳过
right--;
left++;
}
}
}
return result;
}
}
代码分析
这里值得注意的是,若是排序之后第一个数字大于零,那么很明显,没有寻找三个数的必要了,正数相加是不可能等于0的。而且在遍历的过程当中,需要做的就是去重,因为不允许出现相同的情况,我们不仅要对left和right去重之外,我们也需要对i进行去重,这个时候相同的情况下,我们只需要指针左移或者右移动就好.
总结
双指针算法对于和一类,还有接雨水、回文一类的题目还是比较重要的。我们需要构思它是如何移动,并且是怎么移动的,还有什么时候有特殊情况需要指针变化,比如这里的去重。我觉得,还是那句话,多刷题,才能够熟能生巧。当然,还需要多总结,学习里面的做法。
我是小白,期待和大家一起共同探讨力扣,希望能帮到大家,谢谢!