一文彻底解决双指针问题

1.算法思想
双指针主要用于遍历数组,俩指针指向不同的元素,彼此协同完成计算。
主要包括:左右指针与快慢指针

1. 左右指针

167.两数之和
给定一个已按照升序排列 的有序数组,找到两个数使得它们相加之和等于目标数。
函数应该返回这两个下标值 index1 和 index2,其中 index1 必须小于 index2。
说明:
返回的下标值(index1 和 index2)不是从零开始的。
你可以假设每个输入只对应唯一的答案,而且你不可以重复使用相同的元素。
示例:
输入: numbers = [2, 7, 11, 15], target = 9
输出: [1,2]
解释: 2 与 7 之和等于目标数 9 。因此 index1 = 1, index2 = 2 。

解:

class Solution {
    public int[] twoSum(int[] numbers, int target) {
        int[] ans=new int[2];
        int left=0;
        int right=numbers.length-1;
        while(left<right){
            if(numbers[left]+numbers[right]==target){
                ans[0]=left+1;
                ans[1]=right+1;
                return ans;
            }else if(numbers[left]+numbers[right]>target){
                right=right-1;
            }else{
                left=left+1;
            }
        }
        return null;
    }
}

这是一道典型的左右指针的题目。通过左右遍历已排序的数组来找到目标值,大于目标值就左移右指针,小于目标值就右移左指针。

15 三数之和
给你一个包含 n 个整数的数组 nums,判断 nums 中是否存在三个元素 a,b,c ,使得 a + b + c = 0 ?请你找出所有满足条件且不重复的三元组。
注意:答案中不可以包含重复的三元组。
示例:
给定数组 nums = [-1, 0, 1, 2, -1, -4],
满足要求的三元组集合为:
[
  [-1, 0, 1],
  [-1, -1, 2]
]

解:

class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        //新建返回数组
        List<List<Integer>> result = new ArrayList<List<Integer>>();
        //排除特殊情况
        if(nums==null||nums.length<3) return result;
        //先对数组排序
        Arrays.sort(nums);
       //第一层循环遍历,找出比较元素
       for(int index=0;index<nums.length-2;index++){
           //若第一个数大于零,则直接停止寻找
           if(nums[index]>0) break;
           //跳过重复的数据
           if(index>0&&nums[index]==nums[index-1])  continue;
           int first=-nums[index];
           int left=index+1;
           int right=nums.length-1;
            //第二层与第三层可使用左右指针,第二层从左到右,第三层从右到左,找出刚好等于第一层比较元素值的相反数
            while(left<right){
                if(nums[left]+nums[right]==first){
                    List<Integer> tmp=new ArrayList<>();
                    tmp.add(nums[index]);
                    tmp.add(nums[left]);
                    tmp.add(nums[right]);
                    result.add(tmp);
                    left++;
                    right--;
                while(left<right&&nums[left]==nums[left-1]) left++;
                while(left<right&&nums[right]==nums[right+1]) right--;
                }else if(nums[left]+nums[right]<first) left++;
                 else  right--;  
            }
       }
       //将找出的结果加入返回数组
       return result;
    }
}

这道题也是比较典型的左右指针的题目。题目要求三个数之和为0,
第一反应就是三重嵌套遍历每一个组合,但是时间与空间复杂度太大,那么我们需要去除多余的重复选项
第二优化就是给数组排序,然后每次比较前加入判断条件-两数不同,这样从小到大就不会有重复三元组
第三优化在于当结果已经确定,然后进行第一循环时,其实只需要接下来两个数的结果之和等于第一个数相反数即可,应用左右指针,遍历即得答案。

16 最接近的三数之和

给定一个包括 n 个整数的数组 nums 和 一个目标值 target。找出 nums 中的三个整数,使得它们的和与 target 最接近。返回这三个数的和。假定每组输入只存在唯一答案。
示例:
输入:nums = [-1,2,1,-4], target = 1
输出:2
解释:与 target 最接近的和是 2 (-1 + 2 + 1 = 2) 

解:

class Solution {
    public int threeSumClosest(int[] nums, int target) {
        //排序,减少重复值
        Arrays.sort(nums);
        //初始化差值
        int best=9999;
        //第一次遍历,设置比较值
        for(int i=0;i<nums.length;i++){
            if(i>0&&nums[i]==nums[i-1]) continue;
            int left=i+1;
            int right=nums.length-1;
            while(left<right){
                int sum=nums[left]+nums[right]+nums[i];
                if(sum==target) return target;
                if(Math.abs(sum-target)<Math.abs(best-target)) best=sum;
                if(sum>target){
                    int right1=right-1;
                    while(left<right1&&nums[right]==nums[right1]){
                        right1--;
                    }
                    right=right1;
                }else {
                    int left1=left+1;
                    while(left1<right&&nums[left]==nums[left1]){
                        left1++;
                    }
                    left=left1;
                }
            }
        }
        return best;
    }
}

这道题的解题思路与上题15题类似,但需要注意的一点在于,第17和23行的代码中,原本使用的是i++的形式而不是i=i+1,但显示超出时间限制:这个地方注意点在于: a=i++需要先将a=i 然后i=i+1,这个地方直接多了一步的运算量,而使用i=i+1就不会超出时间限制。

左右指针总结

一般左右指针用于寻找某个给定的target值,诸如上述的几数之和,就是通过指针遍历数组找到目标值,这个时候左右指针的优点在于,可以减少一层遍历,极大的降低了时间复杂度。遍历后的数组分别从前后开始寻找,通过与目标值大小的比较每次移动其中一个指针即可。

2. 快慢指针

该算法常用模板为:

//1.初始化快慢指针
        int fast=1;
        int slow=1;
        int n=nums.length;
        //2.开始遍历,若当前元素值与前一个元素值相同,慢指针停止
        while(fast<n){
			//判断条件
            if(nums[fast]!=nums[fast-1]) nums[slow++]=nums[fast];
            fast++;
        }
        //3.返回结果
        return slow;
27.移除元素
给你一个数组 nums 和一个值 val,你需要 原地 移除所有数值等于 val 的元素,并返回移除后数组的新长度。
不要使用额外的数组空间,你必须仅使用 O(1) 额外空间并 原地 修改输入数组。
元素的顺序可以改变。你不需要考虑数组中超出新长度后面的元素。
示例 1:
给定 nums = [3,2,2,3], val = 3,
函数应该返回新的长度 2, 并且 nums 中的前两个元素均为 2。
你不需要考虑数组中超出新长度后面的元素。
示例 2:
给定 nums = [0,1,2,2,3,0,4,2], val = 2,
函数应该返回新的长度 5, 并且 nums 中的前五个元素为 0, 1, 3, 0, 4。
注意这五个元素可为任意顺序。
你不需要考虑数组中超出新长度后面的元素。

解:

class Solution {
    public int removeElement(int[] nums, int val) {
       int fast=0;
       int slow=0;
       int n=nums.length;
       while(fast<n){
           if(nums[fast]!=val) nums[slow++]=nums[fast];
           fast++;
       }
       return slow;
    }
}

比较典型的快慢指针的题目。因为需要在不适用额外空间的情况下删除元素,那么就在遍历的同时更新元素,遇见符合条件的就跳过,其他的更新,通过快慢指针避免了新建一个数组的额外空间。
80.删除排序数组中的重复值Ⅱ

给定一个增序排列数组 nums ,你需要在 原地 删除重复出现的元素,使得每个元素最多出现两次,返回移除后数组的新长度。
不要使用额外的数组空间,你必须在 原地 修改输入数组 并在使用 O(1) 额外空间的条件下完成。
示例 1:
输入:nums = [1,1,1,2,2,3]
输出:5, nums = [1,1,2,2,3]
解释:函数应返回新长度 length = 5, 并且原数组的前五个元素被修改为 1, 1, 2, 2, 3 。 你不需要考虑数组中超出新长度后面的元素。
示例 2:
输入:nums = [0,0,1,1,1,1,2,3,3]
输出:7, nums = [0,0,1,1,2,3,3]
解释:函数应返回新长度 length = 7, 并且原数组的前五个元素被修改为 0, 0, 1, 1, 2, 3, 3 。 你不需要考虑数组中超出新长度后面的元素。

不能有两个以上重复的值,因为慢指针的是已经排好的数组,所以只要快指针不与慢指针减2的数相同即可。

快慢指针总结:

在不适用额外空间的情况下,可以通过快慢指针在原有的数组上即时更改,通过双指针可一边执行比较一遍更新数组,起到节约空间的作用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值