双指针算法习题解答

1.移动零

题目链接:283. 移动零 - 力扣(LeetCode)

题目解析:该题要求将数组中为0的元素全部转移到数组的末尾,同时不能改变非零元素的相对位置。

解题思路:我们可以用变量dest和cur将该数组分为三个区域。如下图

接着我们可以将数组中为0的元素放在[0,dest]的区域,将数组中非0的元素放在[dest+1,cur-1]的区域,而[cur,n-1]是待处理的区域。等数组中全部处理完之后,就如下图所示。

实现思路:

我们用cur来遍历数组,如果cur遇到数据为0的元素,则让cur++。如果cur遇到非0的元素,因为要将非0数据放在[dest+1,cur-1]区域,所以,我们先将dest++,然后交换nums[dest]和nums[cur]的值。

代码实现

java 复制代码
    public void moveZeroes(int[] nums) {
        int dest=-1;
        for(int cur=0;cur<nums.length;cur++){
            if(nums[cur]!=0){
                dest++;
                int tmp=nums[cur];
                nums[cur]=nums[dest];
                nums[dest]=tmp;
            }
        }
    }

2.复写零

题目链接:1089. 复写零 - 力扣(LeetCode)

解题思路:我们可以用一个cur指针和一个dest指针,用cur指针来遍历数组并且用来确定复写的数,用dest来实现复写的操作。

首先,我们想到从前往后复写,但此时我们会发现,当cur遇到0的时候,我们用dest来实现复写的时候,由于0要复写2次,此时,会将cur后面没实现复写的一个数覆盖掉,这样就会漏掉一个数没法实现复写。

所以,我们要从后往前实现复写。

步骤1.首先,我们要找到最后一个复写的数 。

我们也可以用双指针实现找到最后一个复写的数。用cur来遍历数组,我们先让dest指向-1的位置,当cur遇到数据为非0的数,我们让dest向后走两步,如果cur遇到数据为0的数,我们让dest向后走一步,直到dest走到数组的末尾或者大于末尾的位置。

步骤2.其次我们从后向前实现复写的操作,我们也是通过cur来遍历数组,当cur遇到非0的数据,让dest向前走一步,如果cur遇到数据为0的数,我们让dest向前走两步。

但是在此之前,我们要处理一个边界情况,也就是,当我们返现当数组中最后一个要复写的数是0且该数是数组中倒数第二个数的时候,dest会越界。

如下图

最后,我们实现从后向前的复写就行了。

如下图

java 复制代码
    public void duplicateZeros(int[] arr) {
        int cur=0;
        int dest=-1;
        int n=arr.length;
        //寻找最后一个复写的数
        while(cur<n){
            if(arr[cur]!=0){
                dest+=1;
            }else{
                dest+=2;
            }
            if(dest>=n-1){
                break;
            }
            cur++;
        }
        //处理边界情况
        if(dest>=n){
            arr[dest-1]=0;
            dest-=2;
            cur--;
        }
        //实现从后向前复写
        while(cur>=0){
            if(arr[cur]!=0){
                arr[dest--]=arr[cur];
            }else{
                arr[dest--]=0;
                arr[dest--]=0;
            }
            cur--;
        }
    }

3.快乐数

题目链接:202. 快乐数 - 力扣(LeetCode)

解题思路:

通过题目例子,我们来手动演示以下判断该数是否为快乐数的的过程。

通过手动演示,我们发现在判断数据是否为快乐数的过程中,我们发现数据的变化会成为一个环。也就是说,在这个过程中,数据迟早会有一次演变成环中的数。

我们将上图抽象成如下图的情况

当我们抽象成右边图的时候,这就更我们在学习链表中求链表是否存在环的情况很相似。不过在这里,我们不是判断链表中是否有环,而是判断环中的数据是否为1。

前面,我们在解决判断链表中是否有环的时候,用了快慢指针,这道题,我们也可以用快慢指针。

我们每次让slow一次变化一次,让fast一次变换两次。(这里的变换是指在判断是否为快乐数的过程中,数据的变换)。

以一开始的19为例,slow变化一次就是82,fast变化两次就是68。

java 复制代码
    public int bitSum(int n){
        int sum=0;
        while(n!=0){
            int tmp=n%10;
            sum+=tmp*tmp;
            n=n/10;
        }
        return sum;
    }
    public boolean isHappy(int n) {
        int slow=n;
        //由于要进入循环,我们一开始就要将fast放在slow后面
        int fast=bitSum(n);
        while(slow!=fast){
            slow=bitSum(slow);//让slow往后走一步
            fast=bitSum(bitSum(fast));//fast往后走两步
        }
        return slow==1;
    }

拓展:这时候,有人就会疑惑又没有可能一个数据在变换的过程中,会一直变化下去,不会成环呢?

答案就是,数据在进行变化的过程中,变化的数据一定会成环。

证明:

这就涉及到一个鸽巢原理。

鸽巢原理:当有n+1只格子和n个鸽巢的时候,必定会有一个鸽巢的鸽子数量大于等于1,也就是总会有至少两只鸽子共享一个窝。

此时,我们将题目的数据观察题目的数据范围,如下图

n的最大值为2147483647,相同位数的最大值为9999999999,推出数据变化的范围在[1,810]之间。

所以,我们可以达到,数据在变化的过程中,变化的数肯定在[1,810]之间。

我们就算他变化无数次,变化的数据还是在[1,810]之间,所以可以得出变化的过程中,数据一定会成环。

4.盛水最多的容器

题目链接:11. 盛最多水的容器 - 力扣(LeetCode)

解法思路:对撞指针和单调性

我们可以设一个指针left指向数组中的第一个元素,指针right指向最后一个元素,(right-left)的值就是宽,Math(height[left],height[right])就是高,接着根据高和宽求面积。

由于题目要求是求最大的面积,所以,我们要改变left和right的指向的位置,分别求处不同情况下的面积,然后再这些面积中求一个最大值即可。

我们以何种方式来改变left合right指向的位置呢?

我们一次只变一个,要么让left++,要么right--,我们知道面积=宽*高,而宽=right-left,而再left++的过程中或者是right--的过程中,宽度的值一定是减小的。

所以,我们让height[left]和height[right]中数值较小的值移动。

为什么呢?这就涉及到单调性。如下图解释

我们让指向值较小的指针位置变换,实质上是放弃这个较小数的枚举,因为以这个**数枚举的面积都是在单调减小的,**所以,我们就让数值较小的指针移动。

代码实现

java 复制代码
    public int maxArea(int[] height) {
        int n=height.length;
        int left=0;
        int right=n-1;
        int v=0;
        while(left<right){
            int tmp=Math.min(height[left],height[right])*(right-left);
            v=Math.max(v,tmp);
            if(height[left]<height[right]){
                left++;
            }else{
                right--;
            }
        }
        return v;
    }

5.有效三角型个数

题目链接:611. 有效三角形的个数 - 力扣(LeetCode)

解法思路:双指针和单调性

首先,我们先普及一个判断三角型的知识点,如果我们知道a<b<c,那么我们只需判断a+b>c成立,就可以判断这三条边可以组成三角形。

所以,我们可以先给数组进行排序,这是一步优化。

接着,我们就可以先固定一个最大数,因为数组排过序,是一个有序数组,那么这个**最大数的左边的数是肯定都比这个最大数小的。**接着建立如下图的指针位置

这时,我们就来判断a,b,c能否组成三角形。

如果此时a,b,c三条边能组成三角形,那么此时以9为b边,10为c边,能够成三角形的个数就为right-left 个,因为数组是一个有序的数组,那么(left,right)区间里的数都是大于a的数,那么此时就不用通过枚举b边为9的情况下,能组成三角形的情况了。

如下图

遇到这种情况,我们就让**right - -**就行了

此时,会遇到第二种情况,此时nums[left]+nums[right]<nums[i],此时我们直接让left++就行了。因为此时**(left,right)区间都是小于nums[right]的数**了,所以我们此时就不必要以nums[left]为基准,去枚举了,此时,我们直接将此时的2去掉,及让left++就行了。

知道left和right指针相遇,我们在更换最大边,循环以上步骤。

代码实现:

java 复制代码
    public int triangleNumber(int[] nums) {
        Arrays.sort(nums);
        //固定最大数
        int ret=0,n=nums.length;
        for(int i=n-1;i>=2;i--){//固定最大边
            int left=0;
            int right=i-1;
            while(left<right){
                if(nums[left]+nums[right]>nums[i]){
                    ret+=right-left;
                    right--;
                }else{
                    left++;
                }
            }
        }
        return ret;
    }

6.和为s的两个数字

题目链接:LCR 179. 查找总价格为目标值的两个商品 - 力扣(LeetCode)

解法一:暴力枚举法,但是当数组中的数据太多时,会超时。

java 复制代码
    public int[] twoSum(int[] nums, int target) {
        int n=nums.length;
        for(int i=0;i<n;i++){
            for(int j=i+1;j<n;j++){
                if(nums[i]+nums[j]==target){
                    return new int[]{nums[i],nums[j]};
                }
            }
        }
        return new int[]{-1,-1};
    }

解法二:双指针

此时,我们注意题目中的数组是一个有序数组,所以此题我们可以利用数组的单调性和双指针进行优化。

我们分别设置一个left指针和一个right指针,先让left指向数组中的第一个数据,让right指向数组的最后一个数据,然后我们根据left和right指向值的和与target进行比较,不同的情况,让不同指针移动。如下图

此时,left和right指针在移动的过程中会遇到三种情况。

我们先假设left和right指向的值的和为sum。

第一种情况 :sum<target,如果是sum<target的情况,此时因为数组是一个有序数组,而left指向的值是数组中最小的值,right指向的值是数组中最大的值,最小值与最大值的和还是小于target的值,那么此时left指向的值与[left+1,right]区间的任何一个值相加都是小于target的,所以我们就可以大胆得放弃left指向值得枚举,即让left++。

第二种情况:sum>target,如果是sum>target的情况,最小值与最大值的和都大于target了,那么此时right指向的值与[left,right-1]区间的任何一个值相加都是大于target的,所以此时,我们可以大胆的放弃此时right指向值得枚举情况。

第三种情况:sum==target,这种情况直接返回left和right指向的值就行了。

代码实现:

java 复制代码
    public int[] twoSum(int[] price, int target) {
        int n=price.length;
        int left=0;
        int right=n-1;
        while(left<right){
            if(price[left]+price[right]>target){
                right--;
            }else if(price[left]+price[right]<target){
                left++;
            }else{
                return new int[]{price[left],price[right]};
            }
        }
        return new int[]{-1,-1};
    }

7.三数之和

题目链接:15. 三数之和 - 力扣(LeetCode)

题目解析:求数组中三个数的和为0,并且要求三个数是数组下表不同的数,并且结果集中的一个子集不能重复出现,子集中的数据顺序也不做要求。

解法一:双指针法

求三个数的和为0,我们也可以用求两数之和的方法来解决该问题。

无非我们先固定一个数,去求另外两个数之和为固定数的相反数就行了。 求两数之和和第6题的一摸一样。

不过该题我们要去处理几个细节问题。

细节一:去重

当left和right指针遇到相同元素之后,我们要跳过该元素。

当i也遇到重复元素时,i也要跳过相同的元素。

细节二:不漏

当i,left和right遇到一个符合情况的三元组时,我们继续让left++,right--。

为了方便去重,我们可以先对数组排序。

代码实现:

java 复制代码
    public List<List<Integer>> threeSum(int[] nums) {
        int n=nums.length;
        Arrays.sort(nums);//对数组排序
        List<List<Integer>> ret=new ArrayList<>();
        for(int i=0;i<n;){
            if(nums[i]>0) break;//小优化,当nums[i]>0时,就可以跳出循环
            int target=-nums[i];
            int left=i+1;
            int right=n-1;
            while(left<right){
                int sum=nums[left]+nums[right];
                if(sum>target) right--;
                else if(sum<target) left++;
                else{
                    ret.add(Arrays.asList(nums[i],nums[left],nums[right]));
                    left++;
                    right--;
                    while(left<right&&nums[left]==nums[left-1]) left++;//去重
                    while(left<right&&nums[right+1]==nums[right]) right--;//去重
                }
            }
            i++;
            while(i<n&&nums[i]==nums[i-1]) i++;//去重
        }
        return ret;
   }

小细节:我遇到的错误

解法二:暴力枚举法

我们可以 通过排序+枚举+set去重,不过会超时。

java 复制代码
class Solution {
    public List<List<Integer>> threeSum(int[] nums) {
        Arrays.sort(nums);
         List<List<Integer>> ret = new ArrayList<>();
         Set<List<Integer>> set = new HashSet<>();
         int len = nums.length;
        for(int i = 0; i < len; i++) {
            for(int j = i+1; j < len; j++) {
                for(int k = j+1; k < len; k++) {
                    if(nums[i] + nums[j] + nums[k] == 0) {
                        set.add(Arrays.asList(nums[i],nums[j],nums[k]));
                    }
                }
            }
        }
        ret.addAll(set);

        return ret;
    }
}

8.四数之和

题目链接:18. 四数之和 - 力扣(LeetCode)

解题思路:思路和三数之和的思路差不多,不过有个例子时会超出int的最大值,所以需要我们用long来存储。

代码实现:

java 复制代码
    public List<List<Integer>> fourSum(int[] nums, int target) {
        Arrays.sort(nums);
        int n=nums.length;
        List<List<Integer>> ret=new ArrayList<>();
        for(int i=0;i<n;){
            for(int j=i+1;j<n;){
                long aim=(long)target-nums[i]-nums[j];
                int left=j+1;
                int right=n-1;
                while(left<right){
                    int sum=nums[left]+nums[right];
                    if(sum>aim) right--;
                    else if(sum<aim) left++;
                    else{
                        ret.add(Arrays.asList(nums[i],nums[j],nums[left],nums[right]));
                        left++;
                        right--;
                        while(left<right&&nums[left-1]==nums[left]) left++;
                        while(left<right&&nums[right+1]==nums[right]) right--;
                    }
                }
                j++;
                while(j<n&&nums[j-1]==nums[j]) j++;
            }
            i++;
            while(i<n&&nums[i-1]==nums[i]) i++;
        }
        return ret;   
    }
相关推荐
带多刺的玫瑰44 分钟前
Leecode刷题C语言之统计不是特殊数字的数字数量
java·c语言·算法
爱敲代码的憨仔1 小时前
《线性代数的本质》
线性代数·算法·决策树
yigan_Eins1 小时前
【数论】莫比乌斯函数及其反演
c++·经验分享·算法
阿史大杯茶1 小时前
AtCoder Beginner Contest 381(ABCDEF 题)视频讲解
数据结构·c++·算法
დ旧言~2 小时前
【高阶数据结构】图论
算法·深度优先·广度优先·宽度优先·推荐算法
张彦峰ZYF2 小时前
投资策略规划最优决策分析
分布式·算法·金融
The_Ticker2 小时前
CFD平台如何接入实时行情源
java·大数据·数据库·人工智能·算法·区块链·软件工程
爪哇学长3 小时前
双指针算法详解:原理、应用场景及代码示例
java·数据结构·算法
Dola_Pan3 小时前
C语言:数组转换指针的时机
c语言·开发语言·算法
繁依Fanyi3 小时前
简易安卓句分器实现
java·服务器·开发语言·算法·eclipse