算法--003快乐数

题目的地址:

判断链表有环?

我们在学习链表的时候,讲过如何判断链表是否有环的办法,讲解的时候是用的双指针的办法,如果有环,就会在这个环里相遇

算法步骤:

  1. 初始化两个指针,slow(每次走一步)和fast(每次走两步),都指向链表头部。

  2. 开始循环,直到fast为空或者fast的下一个节点为空(表示链表无环)或者slow和fast相遇(表示有环)。

    • 如果fast或fast.next为null,说明链表无环,返回null。

    • 如果slow和fast相遇,说明链表有环,进入步骤3。

  3. 当slow和fast相遇后,将其中一个指针(比如ptr1)重新指向链表头,另一个指针(ptr2)保持在相遇点。

    然后两个指针每次同时移动一步,直到它们再次相遇。相遇的节点就是环的入口节点。

数学原理:

设链表头到环入口的距离为a,环入口到相遇点的距离为b,相遇点到环入口的距离为c。

则环的长度为b+c。

当slow和fast相遇时,slow走过的距离为a+b,fast走过的距离为a+b+n*(b+c),其中n是fast在环中绕的圈数。

因为fast的速度是slow的两倍,所以有:2(a+b) = a+b + n*(b+c) => a = (n-1)*(b+c) + c

这个公式说明,从链表头到环入口的距离a等于从相遇点到环入口的距离c加上n-1圈环的长度。

因此,当两个指针分别从链表头和相遇点以相同速度前进时,它们会在环入口处相遇。

变量定义:

  • a:链表头到环入口的距离

  • b:环入口到相遇点的距离

  • c:相遇点到环入口的距离(顺时针方向)

  • n:快指针在相遇前在环中走的圈数

  • L:环的长度 = b + c

复制代码
头 → 入口 → 相遇点 → 入口(完成环)

慢指针路程:a + b
快指针路程:a + b + n × L
(因为快指针可能已经在环里转了n圈)

由于快指针速度是慢指针的2倍:
2 × (a + b) = a + b + n × L

化简得:
a + b = n × L
a = n × L - b
a = (n - 1) × L + (L - b)
a = (n - 1) × L + c

第一种方法:

两种情况的区别:

情况1:是快乐数(最终到达1)

text

复制代码
序列:n → ... → 1 → 1 → 1 → ...
特点:1的平方和还是1,所以1是一个自循环

快慢指针轨迹:
慢指针:会慢慢走向1
快指针:会更快到达1

最终,它们会在1相遇
情况2:不是快乐数(进入其他循环)

text

复制代码
序列:n → ... → 4 → 16 → 37 → 58 → 89 → 145 → 42 → 20 → 4 → ...
特点:进入一个不包含1的循环

快慢指针轨迹:
慢指针:在循环中前进
快指针:在循环中更快前进

最终,它们会在循环中的某个点相遇(比如4或16等),但绝对不是1
java 复制代码
class Solution {
    
    public int sumOfSquares(int n) {
        int sum = 0;
        while (n > 0) {
            int digit = n % 10;      
            sum += digit * digit;  
            n /= 10;               
        }
        
        return sum;
    }

    public boolean isHappy(int n) {
       int slow = n; 
       int fast = sumOfSquares(n);
       while(slow != fast){
slow = sumOfSquares(slow);
fast = sumOfSquares(sumOfSquares(fast));
       }
       return slow == 1;
    }
}

鸽巢原理(抽屉原理):

这个题有一个n 得取值范围:

我们直接换算成最大得

我们就简单得先计算多少位,是十位数,也就是2^10得九次方 ,那不如我们直接写成最大值以9来计算,十个9

10个9 进行每一位的平方和是810,也就是我们最大的值都小于十个9,所以我们进行的每一位平方和相加肯定也小于810

【1,810】就是我们的巢穴,我们接下来找鸽子,我们的巢穴是1-810

我们肯定超过这个810就会出现重复的的,也就是我们所说的进入环了

方法三:

核心逻辑:

  1. 巢穴数量:1,8101,810 共 810 个可能的平方和结果

  2. 鸽子数量:计算过程中产生的数字序列

  3. 鸽巢原理 :如果有 811 次计算(产生 811 个数字),而只有 810 个可能的结果,那么必然出现重复

  4. 关键判断

    • 如果在循环中出现 1 → 是快乐数

    • 如果循环中不包含 1 → 不是快乐数

text

复制代码
定理:对于函数 f,从任意 n 出发的序列要么:
1. 收敛到不动点 1
2. 进入一个不包含 1 的循环

证明:
假设存在一个包含 1 的循环,但不是 {1}。
那么循环中必有一个数 a ≠ 1,但 f(a) = 1。
但 f(a) = 1 意味着 a 的各位平方和为 1。
满足这个条件的 a 只能是:1, 10, 100, 1000, ...
计算这些数的平方和:
1 → 1
10 → 1
100 → 1
1000 → 1
...
这些数最终都会进入 1→1 的自循环。
因此,任何包含 1 的循环只能是 {1}。
复制代码
想象这样的序列:
... → x → 1 → y → ... → x → ...

从 1 开始:1 → f(1) = 1,不可能得到 y ≠ 1
所以一旦到达 1,就永远卡在 1 了
java 复制代码
class Solution {
    
    public int sumOfSquares(int n) {
        int sum = 0;
        while (n > 0) {
            int digit = n % 10;      
            sum += digit * digit;  
            n /= 10;               
        }
        
        return sum;
    }

    public boolean isHappy(int n) {
        Set<Integer> seen = new HashSet<>();
        
        while (n != 1) {
            n = sumOfSquares(n);
            if (seen.contains(n)) {
                return false;
            }
            seen.add(n);
        }
        return true;  
    }
}
相关推荐
小白菜又菜10 小时前
Leetcode 3432. Count Partitions with Even Sum Difference
算法·leetcode
wuhen_n11 小时前
LeetCode -- 15. 三数之和(中等)
前端·javascript·算法·leetcode
sin_hielo11 小时前
leetcode 2483
数据结构·算法·leetcode
Xの哲學12 小时前
Linux多级时间轮:高精度定时器的艺术与科学
linux·服务器·网络·算法·边缘计算
大头流矢12 小时前
归并排序与计数排序详解
数据结构·算法·排序算法
油泼辣子多加12 小时前
【信创】算法开发适配
人工智能·深度学习·算法·机器学习
一路往蓝-Anbo12 小时前
【第20期】延时的艺术:HAL_Delay vs vTaskDelay
c语言·数据结构·stm32·单片机·嵌入式硬件
Aaron158813 小时前
AD9084和Versal RF系列具体应用案例对比分析
嵌入式硬件·算法·fpga开发·硬件架构·硬件工程·信号处理·基带工程
laocooon52385788613 小时前
插入法排序 python
开发语言·python·算法
wuhen_n13 小时前
LeetCode -- 1:两数之和(简单)
javascript·算法·leetcode·职场和发展