【双指针专题】之快乐数
一、题目描述
编写一个算法来判断一个数
n
是否是快乐数。快乐数 定义为:
- 对于一个正整数,每次将该数替换为它每个位置上的数字的平方和。
- 然后重复这个过程直到这个数变为
1
,也可能是无限循环但始终变不到1
。- 如果这个过程结果为
1
,那么这个数就是快乐数;如果n
不是快乐数,则返回false
。示例 1:
输入: n = 19 输出: true 解释: 19 -> 1*1 + 9*9 = 82 82 -> 8*8 + 2*2 = 68 68 -> 6*6 + 8*8 = 100 100 -> 1*1 + 0*0 + 0*0 = 1
示例 2:
输入: n = 2 输出: false 解释: 2 -> 4 -> 16 -> 37 -> 58 -> 89 -> 145 -> 42 -> 20 -> 4 -> 16
二、思路引导:双指针 / 快慢指针
快乐数 的关键在于判断过程中是否进入了一个循环。我们可以用两种方式来判断:
- 判断是否变为 1
- 判断是否进入无限循环
对于第二种情况,我们可以利用 快慢指针。因为如果一个数进入了循环,快指针必然会追上慢指针。若快慢指针相遇,并且相遇位置的值是
1
,则这个数是快乐数;否则就不是。
快慢指针原理
- 慢指针 每次执行一次平方和操作;
- 快指针 每次执行两次平方和操作;
- 如果快指针和慢指针相遇,说明数进入了循环。
- 如果相遇点是
1
,那么这个数是快乐数;如果不是1
,则说明进入了一个死循环。
三、算法流程
步骤 1:定义平方和函数
为了判断一个数是否是快乐数,我们需要一个函数来计算该数每一位的平方和。
步骤 2:使用快慢指针
初始化慢指针
slow = n
,快指针fast = Sum(n)
,其中Sum
为平方和函数。循环执行以下步骤,直到慢指针和快指针相遇:
慢指针执行一次平方和操作:
slow = Sum(slow)
快指针执行两次平方和操作:
fast = Sum(Sum(fast))
如果慢指针和快指针相遇时,值是
1
,说明这是一个快乐数;否则,它不是快乐数。
四、代码实现(C++)
class Solution {
public:// 返回 n 这个数每一位上的平方和int Sum(int n){int sum = 0;// 循环提取每一位的数字while (n){int t = n % 10; // 提取当前数字的个位sum += t * t; // 将该数字的平方加到总和中n /= 10; // 去掉个位数字,继续处理剩余的部分}return sum; // 返回数字每一位的平方和}bool isHappy(int n){int slow = n, fast = Sum(n); // 慢指针从 n 开始,快指针从平方和开始// 当慢指针和快指针相遇时,判断是否进入了循环while (slow != fast){slow = Sum(slow); // 慢指针每次计算一次平方和fast = Sum(Sum(fast)); // 快指针每次计算两次平方和}// 如果相遇点为 1,则返回 true,表示该数是快乐数;否则返回 falsereturn slow == 1; // 如果慢指针和快指针相遇,并且值是 1,表示该数是快乐数}
};
Sum
函数:
- 该函数用于计算给定数
n
每一位上的平方和。通过循环逐位提取数字,计算平方并累加。- 使用
% 10
提取数字的最后一位,n /= 10
去掉当前位。
isHappy
函数:
- 初始化 慢指针 (
slow
) 为n
,快指针 (fast
) 为Sum(n)
(即n
的每位平方和)。- 在
while
循环中,通过 快慢指针 逐步推进,快指针每次执行两次平方和操作,而慢指针执行一次。- 如果快慢指针相遇,则表示已经进入循环。此时若值为
1
,则返回true
,表示该数是快乐数;否则返回false
,表示进入了无法到达1
的死循环。
五、代码剖析与细节问题
1. 如何计算每一位的平方和?
我们需要逐位提取数的每一位,使用
n % 10
来提取个位,然后计算平方并加到总和中,最后将n /= 10
以去掉个位。这个过程会一直进行,直到n
为0
。
2. 为什么使用快慢指针?
当数进入无限循环时,快指针会追上慢指针。如果快指针在某次相遇时是
1
,则返回true
,表示是快乐数;如果相遇位置不是1
,则返回false
,说明这个数进入了循环。
3. 循环检测的效率如何?
使用快慢指针的好处在于,通过 两次平方和操作,快指针能更快地检测到循环,减少了计算量,确保了算法的高效性。
4. 为什么要判断相遇位置是否是 1 ?
- 如果相遇位置是
1
,表示从此之后快慢指针会一直在1
上循环,这时说明n
是快乐数。- 如果相遇位置不是
1
,则表示数已经陷入了循环并且无法变成1
,所以不是快乐数。
六、复杂度分析
- 时间复杂度:O(log n)
每次计算平方和时,操作的次数是
n
的对数级别,因为数字每次递减一次。通过快慢指针的方式,时间复杂度接近O(log n)
。
- 空间复杂度:O(1)
只使用了常数级别的空间来存储慢指针和快指针,因此空间复杂度为
O(1)
。
七、总结
这道题不仅考察了如何使用快慢指针来解决循环问题,还加深了对 快慢指针 技巧的理解。通过将 快乐数 问题转化为 循环检测,我们能够快速判断一个数是否是快乐数。
实际应用
环形链表问题: 快慢指针常用于检测环形链表问题,能够高效判断链表是否有环。
链表相交点: 快慢指针还可以用来找出两个链表的交点,通过设定不同的起始点并同时移动,最终可以找到交点。
笔者的话:快乐数是一道经典的题目,通过这道题,读者可以熟悉如何用 快慢指针 解决循环检测问题。掌握了这个技巧,在其他类似问题上也会有更多的灵感。