优选算法 力扣 202.快乐数 快慢双指针 解决带环问题 C++解题思路 每日一题
目录
- 零、题目描述
- 一、为什么这道题值得你花几分钟看懂?
- 二、从“序列特性”到“环模型”:思路的转化
- 三、快慢指针实现:判断循环与结果
- 四、完整代码与解析
- 完整代码(附详细注释)
- 代码走读(示例1:`n = 19`)
- 复杂度分析
- 五、常见问题与解决方案
- 六、举一反三
零、题目描述
题目链接:快乐数
题目描述:
示例 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. 辅助函数:计算数字的平方和
首先需要一个函数来计算一个数每个位置上数字的平方和:
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。
bool isHappy(int n)
{int slow = n, fast = bitsum(n);while (slow != fast){slow = bitsum(slow); // 慢指针走一步fast = bitsum(bitsum(fast)); // 快指针走两步} return slow == 1; // 相遇时为1则是快乐数
}
四、完整代码与解析
完整代码(附详细注释)
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
)
- 初始化:
slow = 19
,fast = bitsum(19) = 1² + 9² = 82
- 第一次循环:
slow != fast
slow = bitsum(19) = 82
fast = bitsum(bitsum(82)) = bitsum(8² + 2²) = bitsum(68) = 6² + 8² = 100
- 第二次循环:
slow != fast
slow = bitsum(82) = 68
fast = bitsum(bitsum(100)) = bitsum(1² + 0² + 0²) = bitsum(1) = 1
- 第三次循环:
slow != fast
slow = bitsum(68) = 100
fast = bitsum(bitsum(1)) = bitsum(1) = 1
- 第四次循环:
slow != fast
slow = bitsum(100) = 1
fast = bitsum(bitsum(1)) = bitsum(1) = 1
- 此时
slow == fast
,退出循环,返回slow == 1
,即true
复杂度分析
复杂度类型 | 具体值 | 说明 |
---|---|---|
时间复杂度 | O(log n) | 对于每次计算平方和,数字的位数大约是log n(以10为底),而快慢指针相遇的次数是有限的,整体可看作O(log n) |
空间复杂度 | O(1) | 只使用了常数个额外变量 |
五、常见问题与解决方案
-
为什么快慢指针一定会相遇?
- 因为数字的平方和序列要么最终得到1,要么进入循环。当进入循环时,快指针速度比慢指针快,最终一定会追上慢指针,即相遇。
-
为什么相遇时如果是1就是快乐数?
- 因为如果是快乐数,序列最终会稳定在1,此时快慢指针都会指向1,所以相遇时为1;如果不是快乐数,序列会进入一个不含1的循环,相遇时就不是1。
-
计算平方和时需要考虑负数吗?
- 不需要,因为题目中n是正整数,且平方和的计算结果也一定是非负的,而正整数的平方和计算过程中不会出现负数。
六、举一反三
这道题的快慢指针思路可推广到:
- 检测循环:任何可能出现循环的序列问题,都可以用快慢指针来判断是否有循环以及循环的位置。
- 优化查找:在一些需要重复计算的问题中,可通过快慢指针减少计算次数,提高效率。
下一题预告:LeetCode 11. 盛最多水的容器!这道题会让你亲眼见证双指针的 “收缩魔法”—— 明明是看似需要枚举所有可能的问题,却能通过巧妙移动左右边界,用 O (n) 时间找到最优解。想象一下:两根柱子立在坐标系里,如何快速找到间距与高度的完美平衡?明天的解析会带你拆解其中的贪心逻辑,看完你一定会惊叹:“原来还能这么算!”
最后欢迎大家在评论区分享你的代码或思路,咱们一起交流探讨~ 🌟 要是有大佬有更精妙的思路或想法,恳请在评论区多多指点批评,我一定会虚心学习,并且第一时间回复交流哒!
这是封面原图~ 喜欢的话先点个赞鼓励一下呗~ 再顺手关注一波,后续更新不迷路!