优选算法 力扣 202.快乐数 快慢双指针 解决带环问题 C++解题思路 每日一题

目录

零、题目描述

题目链接:快乐数

题目描述:

示例 1:

输入:n = 19

输出:true

解释:

1² + 9² = 82

8² + 2² = 68

6² + 8² = 100

1² + 0² + 0² = 1
示例 2:

输入:n = 2

输出:false
提示:

1 <= n <= 2^31 - 1

一、为什么这道题值得你花几分钟看懂?

快乐数的判定问题,看似是数学序列的游戏,实则藏着算法中"模型转化"的精髓。它的价值在于:让你学会如何把一个看似陌生的问题,套进熟悉的解题框架里

当你计算数字的平方和时,会发现序列的演变路径和链表惊人地相似------每个数字都像一个节点,平方和的计算过程就是节点指向的"指针"。而题目中"无限循环"的特性,本质上就是链表中"环"的具象化。这种转化能力,能帮你打通 快乐数 与 环形链表(LeetCode 141)的思维壁垒:

  • 就像环形链表中用快慢指针判断是否有环一样,快乐数中同样可以用快慢指针捕捉循环
  • 环形链表找环的入口对应快乐数中循环的起点,而1这个特殊值则对应"无环且终点为1"的理想情况

搞定这道题,你不仅能掌握快慢指针的另一种妙用,更能学会"透过现象看本质"------当未来遇到新问题时,会下意识地思考:"它和我学过的哪个模型相似?" 这种联想能力,才是算法进阶的关键。

如果对环形链表的快慢指针玩法还不熟悉,建议先攻克LeetCode 141,再回头看这道题,会有"原来如此"的顿悟感~

二、从"序列特性"到"环模型":思路的转化

1. 分析快乐数的判定过程

对于一个数n,按照规则计算每个位置上数字的平方和,会得到一个新的数,不断重复这个过程,会形成一个序列。例如对于19:

19 → 82 → 68 → 100 → 1 → 1 → ...

这个序列有两种可能的结局:

  • 最终会得到1,之后一直保持1,这样的数就是快乐数。
  • 进入一个循环,永远不会得到1,这样的数就不是快乐数。

2. 发现与链表环的相似性

观察这个序列,每个数都可以看作是一个节点,从一个数到它的平方和的过程就如同链表中的指针指向。如果序列进入循环,就相当于链表中出现了环。

例如对于不快乐的数2:

2 → 4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4 → ...

从4开始进入了循环,就像链表中4这个节点之后形成了环。

3. 突破口:用判断链表环的方法解决问题

既然快乐数的判定过程可以转化为判断序列是否有环,以及环的入口是否为1,那么判断链表是否带环的快慢指针法就可以派上用场了:

  • 慢指针每次计算一次平方和。
  • 快指针每次计算两次平方和。
  • 如果最终快慢指针相遇时的值为1,就是快乐数;否则,就不是。

三、快慢指针实现:判断循环与结果

1. 辅助函数:计算数字的平方和

首先需要一个函数来计算一个数每个位置上数字的平方和:

cpp 复制代码
int bitsum(int n)
{
    int sum = 0;
    while (n)
    {
        int t = n % 10; // 取出最后一位数字
        sum += t * t;   // 累加该数字的平方
        n /= 10;        // 去掉最后一位数字
    }
    return sum;
}

2. 快慢指针的应用

  • 初始化慢指针slow为n,快指针fast为n的平方和(即bitsum(n))。
  • 循环执行:慢指针每次计算一次平方和,快指针每次计算两次平方和,直到快慢指针相等。
  • 最后判断快慢指针相遇时的值是否为1,若是则返回true,否则返回false。
cpp 复制代码
bool isHappy(int n) 
{
    int slow = n, fast = bitsum(n);
    while (slow != fast)
    {
        slow = bitsum(slow);          // 慢指针走一步
        fast = bitsum(bitsum(fast));  // 快指针走两步
    }  

    return slow == 1; // 相遇时为1则是快乐数
}

四、完整代码与解析

完整代码(附详细注释)

cpp 复制代码
class Solution {
public:
    // 计算一个数每个位置上数字的平方和
    int bitsum(int n)
    {
        int sum = 0;
        while (n)
        {
            int t = n % 10; // 取出最后一位数字
            sum += t * t;   // 累加该数字的平方
            sum += t * t;
            n /= 10;        // 去掉最后一位数字
        }
        return sum;
    }

    bool isHappy(int n) 
    {
        // 初始化慢指针为n,快指针为n的平方和
        int slow = n, fast = bitsum(n);
        // 当快慢指针不相等时,继续循环
        while (slow != fast)
        {
            slow = bitsum(slow);          // 慢指针每次计算一次平方和
            fast = bitsum(bitsum(fast));  // 快指针每次计算两次平方和
        }  

        // 当快慢指针相等时,如果值为1则是快乐数,否则不是
        return slow == 1;
    }
};

代码走读(示例1:n = 19

  1. 初始化:slow = 19fast = bitsum(19) = 1² + 9² = 82
  2. 第一次循环:slow != fast
    • slow = bitsum(19) = 82
    • fast = bitsum(bitsum(82)) = bitsum(8² + 2²) = bitsum(68) = 6² + 8² = 100
  3. 第二次循环:slow != fast
    • slow = bitsum(82) = 68
    • fast = bitsum(bitsum(100)) = bitsum(1² + 0² + 0²) = bitsum(1) = 1
  4. 第三次循环:slow != fast
    • slow = bitsum(68) = 100
    • fast = bitsum(bitsum(1)) = bitsum(1) = 1
  5. 第四次循环:slow != fast
    • slow = bitsum(100) = 1
    • fast = bitsum(bitsum(1)) = bitsum(1) = 1
  6. 此时slow == fast,退出循环,返回slow == 1,即true

复杂度分析

复杂度类型 具体值 说明
时间复杂度 O(log n) 对于每次计算平方和,数字的位数大约是log n(以10为底),而快慢指针相遇的次数是有限的,整体可看作O(log n)
空间复杂度 O(1) 只使用了常数个额外变量

五、常见问题与解决方案

  1. 为什么快慢指针一定会相遇?

    • 因为数字的平方和序列要么最终得到1,要么进入循环。当进入循环时,快指针速度比慢指针快,最终一定会追上慢指针,即相遇。
  2. 为什么相遇时如果是1就是快乐数?

    • 因为如果是快乐数,序列最终会稳定在1,此时快慢指针都会指向1,所以相遇时为1;如果不是快乐数,序列会进入一个不含1的循环,相遇时就不是1。
  3. 计算平方和时需要考虑负数吗?

    • 不需要,因为题目中n是正整数,且平方和的计算结果也一定是非负的,而正整数的平方和计算过程中不会出现负数。

六、举一反三

这道题的快慢指针思路可推广到:

  • 检测循环:任何可能出现循环的序列问题,都可以用快慢指针来判断是否有循环以及循环的位置。
  • 优化查找:在一些需要重复计算的问题中,可通过快慢指针减少计算次数,提高效率。

下一题预告:LeetCode 11. 盛最多水的容器!这道题会让你亲眼见证双指针的 "收缩魔法"------ 明明是看似需要枚举所有可能的问题,却能通过巧妙移动左右边界,用 O (n) 时间找到最优解。想象一下:两根柱子立在坐标系里,如何快速找到间距与高度的完美平衡?明天的解析会带你拆解其中的贪心逻辑,看完你一定会惊叹:"原来还能这么算!"

最后欢迎大家在评论区分享你的代码或思路,咱们一起交流探讨~ 🌟 要是有大佬有更精妙的思路或想法,恳请在评论区多多指点批评,我一定会虚心学习,并且第一时间回复交流哒!

这是封面原图~ 喜欢的话先点个赞鼓励一下呗~ 再顺手关注一波,后续更新不迷路!

相关推荐
爱掉发的小李17 分钟前
Linux 环境下 Docker 安装与简单使用指南
java·linux·运维·c++·python·docker·php
爱编程的鱼1 小时前
计算机(电脑)是什么?零基础硬件软件详解
java·开发语言·算法·c#·电脑·集合
澄澈i1 小时前
设计模式学习[17]---组合模式
c++·学习·设计模式·组合模式
洛生&1 小时前
【abc417】E - A Path in A Dictionary
算法
亮亮爱刷题1 小时前
算法提升之数学(快速幂+逆元求法)
算法
恣艺1 小时前
LeetCode 124:二叉树中的最大路径和
算法·leetcode·职场和发展
1白天的黑夜12 小时前
前缀和-1314.矩阵区域和-力扣(LeetCode)
c++·leetcode·前缀和
weisian1512 小时前
力扣经典算法篇-42-矩阵置零(辅助数组标记法,使用两个标记变量)
算法·leetcode·矩阵
恣艺2 小时前
LeetCode 123:买卖股票的最佳时机 III
算法·leetcode·职场和发展
geoyster2 小时前
20250802-102508010-CP
算法