“双指针”算法上篇

『 你 的 名 字 』高帧4K动漫素材,无水印,需要自取!


题目:
一· 移动零
1.题目链接:移动零

2. 分析

解法一:暴力求解

就是新开一个数组,大小与原数组大小一致,只要不是数字0就进行写入,若是数字0,统计出现的

次数,最后把所有非0元素写入之后,在写入0元素。

对于一些没有思路的,老铁,刚刚看到此题,或许都会这样

但是,题目要求空间复杂度必须是O(N),所以这个想法就 “pass ” 掉了。

 解法二:双指针算法

3. 原理

双指针:注意这里可不是真正意义上的指针:int*,char* ,float* …… 而是指向元素的下标

cur 指针:指向当前要扫描的元素

dest指针:指向已经被扫描过的元素

初始位置:dest  = 0; cur = 0 (表示从左向右依次进行判断是否为数字0)

 cur 指向元素为0,则++cur

cur 指向元素非0,交换 cur,dest对应位置的元素,同时++cur,++dest

 

 这里建议,咱还是先根据自己的理解,来编写程序,看看自己上手能力咋样哈~~~

 

4.OJ代码
class Solution {
public:
    void moveZeroes(vector<int>& nums)
     {
        int dest = 0,cur = 0;
        int n = nums.size();
        for(;cur < n;++cur)
        {
            if(nums[cur] )
            {
                swap(nums[dest++],nums[cur]);
            }
           
        }

    }
};
二· 复写零
1.题目链接:复写零

2. 分析

解法一:暴力求解

还是老样子:新开一个数组,对于非0元素进行“复写”一遍,0元素,“复写” 两遍,可以解决问题,

但是不符合要求:必须原地修改数组(也就是说时间 复杂度 O(N))

解法二:双指针

模拟“异地”元素的移动:开一个与原始数组大小一样 的空间,对于非0元素进行“复写”一遍,0元

素,“复写” 两遍,我们可以找到新数组的最后一个元素,

假设我们知道“复写”之后的数组的最后一个元素,这个问题就简化了许多。此时就是对元素进行“复

写”的问题了:从前往后还是从后往前进行写入???

误区一:从前往后遍历

定义2个指针:cur = 0,dest = 0;

当cur 指向非0元素,arr[dest ]  = arr [ cur ] ,++dest,++cur

当cur 指向0元素:arr[dest ]  = arr [ cur ] ,++dest,  arr[dest ]  = arr [ cur ] ,++dest,++cur

 这样势必会造成数据覆盖,

所以说应该从后往前进行写入。

问题又来了:如何找到数组写入之后的最后一个元素呢?还是借助双指针

3. 原理

1)先模拟找到需写入数组的最后一个元素

图解:

双指针: dest = -1,cur = 0,cur 对应元素0,dest += 2,++cur 

注意:避免dest 越界,所以一个判读dest 是否 越界 dest >= n-1 

cur 对应非0元素:++dest,++cur

 结束之后:cur 位置对应的一定是写入数组的最后一个元素的位置

注意:结束之后dest 可能不是数组的有效位置

当cur 对应位置是元素0,会导致dest 越界,此时应该在dest 原有位置 前移动 2步,同时把最后一

个位置写入0即可

对于这个问题:可能会说直接让  dest = n-1 ,不就完了吗,反正最后dest  也是从数组的最后一个

位置开始“复写”

但是:可能恰好,cur 对应位置是元素0,写入数组的时候,就还只有一个位置,如果只是简单的

让dest = n- 1 ,对于这个临界情况就搞不了

临界情况判断:

 if (arr[cur] == 0 && dest > n-1)//注意这里后面的dest 的判断必须加上;也有可能复写的最后一个元素是0同时dest向后移动,刚刚到了n-1对应位置
    {
        arr[n - 1] = 0;
        dest -= 2;//注意永远指向下一个要填写的位置
        --cur;
    }
4.OJ代码
class Solution {
public:
  void duplicateZeros(vector<int>& arr)
{
    // 1:需要找到新数组最后一个元素,以方便复写从哪里开始

    int cur = 0, dest = -1;
    int n = arr.size();
    while (cur <= n - 1)
    {
        if (arr[cur] != 0)
        {
            ++dest;
        }
        else
        {
            dest += 2;
        }
        if (dest >= n - 1)
        {
            //对dest 需要判断是否越界
            break;
        }
        ++cur;
    }
    //cur 一定是复写元素的最后一个,同时dest 永远指向的是要复写下一个元素位置
    // // 2:临界判断:可能结束的时候恰好cur遍历到原始数组元素为0
    //导致dest 越界
    //注意不能简单的让dest = n-1 ,这样就可能存在数据覆盖
    if (arr[cur] == 0 && dest > n-1)//注意这里后面的dest 的判断必须加上;也有可能复写的最后一个元素是0同时dest向后移动,刚刚到了n-1对应位置
    {
        arr[n - 1] = 0;
        dest -= 2;//注意永远指向下一个要填写的位置
        --cur;
    }
   
    // 3: 双指针:从后往前进行复写
   
    while (cur >= 0)
    {
        if (arr[cur])
        {
            arr[dest--] = arr[cur--];
        }
        else
        {
            arr[dest--] = arr[cur];
            arr[dest--] = arr[cur--];
        }
    }

}
};
三· 快乐数
1.题目链接:快乐数

2. 分析

对于数字n  ,就是拿到每一位的数字,并把每一位数字的平方进行相加求得平方之和,一直重复这

个过程,当变为1,就是快乐数,否则就不是快乐数。

 通过画图我们知道,对于一个数字是快乐数也好,不是快乐数也好,最终都会进入一个环,此时

问题就变成了判断是否为数字1 了。

不知道到这里,是否想起链表里面的一个OJ题目:判断链表是否有环?

对于这个题目感到陌生的友友们,可以看看之前我写的这个题解判断链表是否有环

 所以对于这个问题的思路:快慢指针

3. 原理

定义2个指针:

slow 指针初始位置指向 n 所得的数字平方之和

fast 指针 初始位置指向 :slow 指针初始位置指向 n 所得的数字平方之和的平方之和

最终2指针一定相遇,所以就只需要判断其中一个指针是否为1即可

4.OJ代码
class Solution {
    //求当前数字每一位的平方和
    int num_sum(int n)
    {
        int sum = 0;
        while(n)
        {
            int x = n%10;
            sum += (x*x);
            n /= 10;
        }
        return sum;
    }
public:
    bool isHappy(int n)
     {
        //题型:判断链表是否有环
        //快慢指针(也不一定就是指针)
        int slow = n;
        int fast = num_sum(n);
        while(fast != slow)
        {
            //fast 走2步
            //slow 走1步
            slow = num_sum(slow);
            fast = (num_sum(num_sum(fast)));
        }
        return fast == 1;

    }
};

总结:

对于快慢指针这一思想的应用关键在于:初始位置下标的确定;如何迭代;还是需要注意一些临界

情况判断以及特例

  • 33
    点赞
  • 34
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值