当前位置: 首页 > news >正文

[Lc优选算法] 双指针 | 移动零 | 复写零 | 快乐数

目录

🎢1.移动零

题解

代码

⭕2.复写零

题解

代码

⭕3.快乐数

题解

代码


🎢1.移动零

题目链接:283. 移动零

题目描述

给定一个数组 nums,编写一个函数将所有 0 移动到数组的末尾,同时保持非零元素的相对顺序。

请注意 ,必须在不复制数组的情况下原地对数组进行操作。

示例 1:

输入: nums = [0,1,0,3,12]
输出: [1,3,12,0,0]

示例 2:

输入: nums = [0]
输出: [0]

提示:

  • 1 <= nums.length <= 104
  • -231 <= nums[i] <= 231 - 1

进阶:你能尽量减少完成的操作次数吗?


题解

这类题可以归类到一类提里面【数组划分、数组分块】

  • 这类题的特点就是:给一个数组然后制定一个规则,在这个规则下把这个数组划分称若干区间

解决这类题首选:双指针算法 ,数组中用双指针利用数组下标充当指针。

两个指针的作用:

  • cur:从左往右扫描数组,遍历数组
  • dest:已处理区间内,最后一个元素(本题是最后一个非0元素)

两个指针分三个区间:

  • 【0,dest】已处理区间都是非0元素
  • 【dest+1,cur-1】已处理区间都是0元素
  • 【cur,n-1】待处理元素

  • n 个元素 ,下标为 n-1

初始时,cur在下标 0 的位置,dest位-1,可能初始时没有非0元素。

cur 从前往后遍历的过程中:

  1. 遇到 0 元素:cur++
  2. 遇到 非零元素:
    swap(++dest,cur)//将非 0 元素和 ++dest 位置交换,dest 所在区间就为 已处理 非零元素了
    cur++

代码

//如果 cur 不是 0,就和++dest 目标位置 进行交换,实现非 0 元素,都移到前面


⭕2.复写零

题目链接:1089. 复写零

题目描述

给你一个长度固定的整数数组 arr ,请你将该数组中出现的 每个零都复写一遍,并将其余的元素向右平移。

注意:请不要在超过该数组长度的位置写入元素。请对输入的数组 就地 进行上述修改,不要从函数返回任何东西。

示例 1:

输入:arr = [1,0,2,3,0,4,5,0]
输出:[1,0,0,2,3,0,0,4]
解释:调用函数后,输入的数组将被修改为:[1,0,0,2,3,0,0,4]

示例 2:

输入:arr = [1,2,3]
输出:[1,2,3]
解释:调用函数后,输入的数组将被修改为:[1,2,3]

提示:

  • 1 <= arr.length <= 1040
  • 0 <= arr[i] <= 9

题解

一个数组,不开辟另一个数组,在原数组内解决问题。

  • 解决方法:双指针算法

先根据 “异地” 操作,然后优化成双指针下的 “就地” 操作。

双指针 从左往右,我们发现dest跑到cur前面去了,对于本道题这是不行的。

那试一试从右到左,dest肯定是最后一个数组下标位置,但是cur不好弄,如果初始cur==dest,然后同样会覆盖。

因此我们想办法让cur初始时指向数组最后一个复写的数。如果这样复写

1. 先找到最后一个 “复写” 的数

这里还可以再用一次双指针算法,双指针还能套双指针)

查找 就可以 去想 双指针~)

  • 先判断 cur 位置的值
  • 决定 dest 向后走一步或者两步

  • 判断一下 dest 是否已经到结束 为止
  • cur++

但这里有个问题,dest越界的问题,所以要处理一下边界情况。

cur位置的值是0,dest越界,处理越界问题:下标【n-1】复写0,dest-=2,cur-=1;

2.“从后向前” 完成复写操作

代码

//cur 当前未处理 //实现 判断移动
//dest 目标
class Solution {
public:

//双指针
//先到最后预判
//从右往左

    void duplicateZeros(vector<int>& arr) 
    {
        int cur=0,dest=-1;
        int n=arr.size();

        //找到 最后一个复写位置
        while(cur<n)
        {
            if(arr[cur])
            {
                dest++;
            }
            else
            {
                dest+=2;
                //为空 就移两步
            }

            if(dest>=n-1)
            {
                break;//到 最后一位 及以后了
                //就停止 循环
            }

            cur++;
        }

        //处理 一下边界情况
        if(dest==n)
        {
            arr[n-1]=0;
            cur--;
            dest-=2;//
        }

        //从后往前 填写
        while(cur>=0)
        {
            if(arr[cur])
            {
                arr[dest--]=arr[cur--];
            }
            else{
                arr[dest--]=0;
                arr[dest--]=0;
                cur--;
            }
        }
        
    }
};

⭕3.快乐数

题目链接:202.快乐数

题目描述

编写一个算法来判断一个数 n 是不是快乐数。

「快乐数」 定义为:

  • 对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
  • 然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
  • 如果这个过程 结果为 1,那么这个数就是快乐数。

如果 n快乐数 就返回 true ;不是,则返回 false

示例 1:

输入:n = 19
输出:true
解释:
12 + 92 = 82
82 + 22 = 68
62 + 82 = 100
12 + 02 + 02 = 1

示例 2:

输入:n = 2
输出:false

题解

解释:(这里省去计算过程,只列出转换后的数)

2 -> 4 -> 16 -> 37 -> 58 -> 89 -> 145 -> 42 -> 20 -> 4 -> 16

往后就不必再计算了,因为 出现了重复的数字,所以最后结果肯定不会是 1

分析:

为了方便叙述,将【对于一个正整数,每⼀次将该数替换为它每个位置上的数字的平⽅和】这一个操作记为 x 操作;

题目告诉我们,当我们不断重复 x 操作的时候,计算⼀定会【死循环】,死的方式有两种:

  • 情况一:⼀直在 1 中死循环,即 1 -> 1 -> 1 -> 1…

(!重点就是 对于 111 也是一个死循环的理解~)(所以都是循环,只是循环结果不同)

  • 情况二:在历史的数据中死循环,但始终变不到 1

由于上述两种情况只会出现⼀种,因此

只要我们能确定循环是在【情况⼀】中进行,还是在【情况二】中进行,就能得到结果。

简单证明:

  1. 经过一次变化之后的最大值 9^2 * 10 = 810 ( 2^31-1=2147483647 。选一个更大的最大 9999999999 ),也就是变化的区间在 [1, 810] 之间;
  2. 根据【鸽巢原理】,一个数变化 811 次之后,必然会形成一个循环;(*详可见代码后批注)
  3. 因此,变化的过程最终会走到一个圈里,因此可以用【快慢指针】来解决

算法原理:

以前做过一种题,判断链表是否有环,我们用的是快慢双指针,

  1. 定义快慢指针
  2. 慢指针每次向后移动一步,快指针每次向后移动两步
  3. 判断相遇时候的值即可

这里我们不能固定思维,快慢指针是一个思想,可以让不同东西代替指针

这道题我们可以让某个数代替双指针。

代码

class Solution {
public:
// 1.先对 每个位置上的 数字拆分 进行实现
//细分

    // sum
    // tmp
    // n
    int BitSum(int n) {
        int sum = 0, a = n;
        while (a) {
            int tmp = a % 10;
            sum += tmp * tmp;
            a = a / 10;
        }
        return sum;
    }

    bool isHappy(int n) {
        // 可以通过bitSum来实现 对每一步的数字和 进行计算了

// 2.
        int slow = n, fast = BitSum(n);
        while (slow != fast) {
            slow = BitSum(slow);
            fast = BitSum(BitSum(fast)); // 移 两步
        }
        // 直到 快慢指针 判完环之后

        return slow == 1;
    }
};

注:变化的最大值和鸽巢原理

  1. 变化的最大值:考虑到一个数字每一位最大可能是9,因此最大的一位数的平方是81。对于一个十位数而言(尽管实际上由于2^31-1的限制,我们不会遇到这么大的数),其经过一次x操作后的最大可能结果是9^2×10=810。这意味着,无论原始数字多大,经过一次x操作之后,得到的结果都不会超过810。
  2. 鸽巢原理的应用:鸽巢原理(又称抽屉原理)简单地说就是如果有更多的鸽子而鸽巢数量有限,则至少有一个鸽巢里会有多于一只的鸽子。在这个问题中,由于知道经过x操作后数值范围被限制在[1, 810]之间,如果连续进行811次变换,根据鸽巢原理,(只要 变换的次数足够多)至少有一次变换的结果会与之前的某次变换结果相同,从而形成循环
  3. 快慢指针方法:一旦确定了循环的存在,就可以 使用快慢指针的方法来检测循环。这种方法通常用于链表中的环检测,但在这里也可以用来识别数字序列何时开始重复。快指针每次走两步,慢指针每次走一步,如果存在循环,两个指针最终会在环内相遇。

相关文章:

  • 从0搭建Tomcat第二天:深入理解Servlet容器与反射机制
  • 在QML中注册C++类型
  • 如何学习人工智能(如Transformer架构和DeepSeek等)
  • JavaFunction的使用
  • 在2023idea中如何创建SpringBoot
  • 大数据-240 离线数仓 - 广告业务 测试 ADS层数据加载 DataX数据导出到 MySQL
  • 建筑兔零基础自学python记录35|编程练习1
  • 首页分包,跳转,以及分类导航区
  • 六十天前端强化训练之第一天到第七天——综合案例:响应式个人博客项目
  • FPGA开发,使用Deepseek V3还是R1(2):V3和R1的区别
  • 207. 课程表(dfs)
  • 肖恩的n次根 (二分)
  • Spring Boot拦截器(Interceptor)与过滤器(Filter)详细教程
  • uni.getLocation 微信小程序中获取位置失败原因
  • 《论软件测试中缺陷管理及其应用》审题技巧 - 系统架构设计师
  • 利用PyQt简单的实现一个机器人的关节JOG界面
  • 行为型模式 - 迭代器模式 (Iterator Pattern)
  • SpringCloud + Spring AI Alibaba 整合阿里云百炼大模型
  • 【大模型】大模型推理部署工具之vLLM的使用(1)
  • 在Nginx上配置并开启WebDAV服务的完整指南
  • 常德网站建设求职简历/百度发广告需要多少钱
  • 网站制作交流论坛/百度推广一个月多少钱
  • 纯mvc做的都有那些网站/seo点击工具帮你火21星热情
  • 哈尔滨政府网站建设/app营销策略有哪些
  • 教你做文案的网站推荐/百度云盘官网登录入口
  • 响应式网站建设有哪些好处/电商网站建设价格