双指针 -- 移动零、复写零、快乐数

目录

移动零

题解:

复写零

题解:

快乐数

题解:

盛最多水的容器


移动零

283. 移动零 - 力扣(LeetCode)icon-default.png?t=O83Ahttps://leetcode.cn/problems/move-zeroes/description/

题解:

题目要求我们把数组中的 0 放在数组的末尾,非零数按照原来的相对顺序,可以用快排的思想来解决这道题。

定义两个指针 dest 和 cur,这两个指针对数组进行了分类,下标从 0 到 dest 的数组区间存的是非零数,下标从 dest+1 到 cur 的数组区间存的是,下标从 cur+1 到数组的结尾部分还没有处理过的数,需要 cur 继续往后遍历来进行处理。

如何实现上面的分类呢?

从上面的分析上,我们发现 dest 是划分非零数和 0 的界限,arr[ dest ] 是非零数,arr[ dest+1 ] 就是 0,而 cur 的作用是为了找 0

  • 如果 cur 遍历到的数是非零数,那么交换 arr[ dest+1 ]  和 arr[ cur ] ,同时 dest++,dest 就可以继续发挥划分界限的作用!
  • 如果 cur 遍历到的数是 0,不必交换,直接 ++cur 。

用题目中的例1来进行分析:

class Solution {
public:
    void moveZeroes(vector<int>& nums) {
        for(int dest=-1,cur=0;cur<nums.size();++cur)
        {
            if(nums[cur]!=0)   
                swap(nums[++dest],nums[cur]);
        }
    }
};

复写零

1089. 复写零 - 力扣(LeetCode)icon-default.png?t=O83Ahttps://leetcode.cn/problems/duplicate-zeros/description/

题解:

由于题目要求在原地修改数组,所以我们不可以重新开辟一个新的数组进行异地修改,但是可以通过异地修改来整理本题的思路。

以示例 1 为例,复写之后超出数组范围的数将会被删掉。

 如果我们从前往后模拟复写过程,比如我们用 i 来遍历数组,当 arr[ i ] 为 0 时,让 arr[ i+1 ] 复写为 0,那么 arr[ i+1 ] 原本的数就被覆盖了,最终得到的答案一定是错误的。

所以采用从后往前的方式, 那么从后往前要从哪里开始遍历,才可以避免复写时发生数组越界呢?

我们可以先用两个指针来模拟一下复写过程,得出复写后的数组的最后一个数是谁。

指针 i 用来找出复写后的数组的最后一个数是谁,指针 top 用来遍历数组,

  • 如果 arr[ i ] == 0,那么 top += 2 ;
  • 如果 arr[ i ] != 0,那么 top++ ;

其实指针 i 是在模拟复写的过程,在找出复写后的元素是什么,而指针 top 是在模拟 arr[ i ] 复写后会怎么移动,指向 arr[ i ] 移动后的位置的下一个下标

当 top 越界时,说明复写过程已经模拟完成了,结束模拟,此时的 arr[ i ] 就是复写后的数组的最后一个元素

找到复写完的数组的最后一个元素后,就可以开始进行复写操作了,指针 j 从数组的最后一个元素开始遍历,

  • arr[ i ] !=0 ,此时 arr[ i ] 不需要复写,则  arr[ j ] = arr[ i ];
  • arr[ i ] ==0,此时 arr[ i ] 需要复写,则  arr[ j ] = arr[ i ] 之后,--j,把 0 再写一次

完成一次复写操作之后,把 i 和 j 都减 1 即可。

上面的模拟还存在一种特殊情况,如果复写后的最后一个数是 0 呢?

比如下面的数组,arr = [ 1, 0, 2, 3, 0, 4 ],先进行复写模拟:

可以发现复写后得到的数组的最后一个元素为 0 ,但是这个 0 是不需要复写的,或者说复写后的第二个 0 超出了数组的范围,被砍掉了。这时候再按照上面的复写过程,就会得到错误的结果。

所以我们需要在复写之前,对指针进行修正:

  • 如果复写后的数组的最后一个元素不是 0,则 top == n; 
  • 如果复写后的数组的最后一个元素为 0 ,才会导致 top == n+1。

 如果 top == n+1,则先把复写后的数组的最后一个元素填写为 0,然后指针 i 和 j 都向前移动一步,这样就可以保证复写后的数组的正确性。

 复写过程如下:

class Solution {
public:
    void duplicateZeros(vector<int>& arr) {
        //模拟复写过程,找出复写后数组的最后一个数
        int top=0,i=-1,n=arr.size();
        while(top<n)
        {
            i++;//i一个个遍历数组
            //top模拟复写后的情况
            if(!arr[i]) top+=2;//是0,要复写,所以+2
            else top++;//不是0,不用复写,所以+1
        }

        int j=n-1;
        if(top==n+1)//说明复写后的数组的最后一个元素为0
        {
            arr[n-1]=0;
            j--;    i--;
        }

        //开始复写
        while(j>=0)
        {
            arr[j]=arr[i]; --j;
            if(arr[i]==0)//复写
            {
                arr[j--]=0;
            }
            i--;
        }
    }
};

快乐数

202. 快乐数 - 力扣(LeetCode)icon-default.png?t=O83Ahttps://leetcode.cn/problems/happy-number/description/

题解:

由于 n 是 int 类型,int 类型下能表示的最大正整数为 2147483647,按照题目的要求,对于一个正整数,把正整数的每一位置的数的平方后相加,这个和的最大值和最小值是多少呢?

在 int 范围内,当正整数的每一位置的数都尽可能大时,平方和就可以达到最大值,当这个正整数为 19 9999 9999 时(29 9999 9999 已经超出 int 的范围了),平方和达到最大,为  81*9 + 1=730 。反之,当正整数的每一位置的数都尽可能小时,平方和就可以达到最小值,显然,平方和的最小值为 1。所以平方和的取值范围为 1 到 730 .

也就是说,根据抽屉原理,一个数 n 经过无数次 求每一位置的数的平方和 时,一定会存在重复的平方和,如果这个重复的平方和为 1,那么 n 就是快乐数,如果不是 1,那么 n 就不是快乐数!

那么我们怎样得到这个重复的平方和呢?

按照题目的暗示,求平方和的过程是一个循环,既然是循环,就可以按照快慢指针的思想(快指针的速度是慢指针的两倍),快指针比慢指针多求一次平方和,在一个循环中,快慢指针最终一定会相遇!当快慢指针相遇时,也就是出现了重复的平方和,如果这个重复的平方和为 1,那么 n 就是快乐数,如果不是 1,那么 n 就不是快乐数!

为什么初始化时慢指针为 n,快指针先求了一(两)次平方和,而不是让快慢指针都初始化为 n ?

因为我们用了 while 来模拟快慢指针的循环过程,当快慢指针相遇时,即 快慢指针相等时,while 循环结束,如果我们一开始就把快慢指针都初始化为 n,是没办法进入循环过程的!

class Solution {
public:
    int Pow(int num)
    {
        int ret=0;
        while(num)
        {
            ret+=pow(num%10,2);
            num/=10;
        }
        return ret;
    }
    bool isHappy(int n) {
        int fast=Pow(n),slow=n;
        //int fast=Pow(Pow(n)),slow=n; 也可以
        while(fast!=slow)
        {
            fast=Pow(Pow(fast)),slow=Pow(slow);
        }
        if(fast==1 && slow==1) return true;
        else return false;
    }
};

盛最多水的容器

11. 盛最多水的容器 - 力扣(LeetCode)icon-default.png?t=O83Ahttps://leetcode.cn/problems/container-with-most-water/description/

class Solution {
public:
    int maxArea(vector<int>& height) {
        int left=0,right=height.size()-1,ret=0;
        while(left<right)
        {
            int tmp=min(height[left],height[right])*(right-left);
            ret=max(tmp,ret);

            //移动指针
            if(height[left]>height[right]) --right;
            else ++left;
        }
        return ret;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值