循环检测算法(哈希,双指针)
快乐数
编写一个算法来判断一个数 n 是不是快乐数。
「快乐数」 定义为:
对于一个正整数,每一次将该数替换为它每个位置上的数字的平方和。
然后重复这个过程直到这个数变为 1,也可能是 无限循环 但始终变不到 1。
如果这个过程 结果为 1,那么这个数就是快乐数。
如果 n 是 快乐数 就返回 true ;不是,则返回 false 。
各位数字上的平方之和再相加,最后如果得到了数字1,则这个数字就是快乐数,否则就不是。
快乐数具有以下的特性:
- 数字满足条件,每位的数字平方后相加,最后的结果是1
- 最后结果无法等于1,会进入循环。
数字有没有可能会无限大且没有循环?
答: 不可能。
例如数字: 2147483648,把他每一位拆分计算后:它肯定就会变成一个三位的数字,因此它不可能无限大下去,它一定会有一个循环或者最后结果为1.
方法一:
利用 哈希集和 统计计算的每一次的结果,如果陷入了循环,则哈希集合将显示为已经存在,因此此时就可以退出循环,即最后的结果为false;如果结果为1,则结束循环,返回true。
class Solution { public: int getnum(int n) { int sum=0; while (n) { sum+=(n%10) * (n%10); n/=10; } return sum; } bool isHappy(int n) { unordered_set<int> s; while (n!=1 && !s.count(n)) { s.insert(n); n=getnum(n); } return n==1; } };
方法二:
利用快慢指针,又叫龟兔赛跑法。
- 一个快指针,一个慢指针。
- 快指针每次走两步,慢指针一次走一步,当他们最后陷入循环的时候,快指针总是能追上慢指针。
- 快指针自开始后必须每次都快于慢指针两步
- 快指针为1,则结果为1,返回true
- 快指针如果进入了循环,则它一定会等于慢指针,因此当他们相等的时候,循环结束,返回false。
class Solution { public: int getnum(int n) { int sum=0; while (n) { sum+=(n%10) * (n%10); n/=10; } return sum; } bool isHappy(int n) { int slow=n; int fast=getnum(n); while (fast!=1 && fast!=slow) { slow=getnum(slow); fast=getnum(getnum(fast)); } return fast==1; } };
环形链表
给你一个链表的头节点 head ,判断链表中是否有环。
对于循环链表,我们也采用这种循环检测的算法,即双指针或者哈希集合的方式:
首先来哈希集和:
- 利用哈希集合来存储每一个节点,如果在之后遍历到的节点的存在于哈希集合中,则结束循环,此链表是环形的。
- 如果一直遍历到了空节点,则不是环形的。
class Solution { public: bool hasCycle(ListNode *head) { unordered_set<ListNode*> s; ListNode* p=head; while (p!=nullptr && !s.count(p)) { s.insert(p); p=p->next; } return p!=nullptr; } };
方法二:
快慢指针法:
- 快指针每次走两步,慢指针每次走一步,快指针必须领先于慢指针两步。
- 快指针在某时刻碰见了慢指针,则是环形的,否则如果快指针到了nullptr,则不是环形的。
class Solution { public: bool hasCycle(ListNode *head) { if (!head || !head->next) { return false; } ListNode* pfast=head->next; ListNode* pslow=head; while (pfast!=nullptr && pfast!=pslow) { pslow=pslow->next; pfast=pfast->next; if (pfast!=nullptr) { pfast=pfast->next; } } return pfast!=nullptr; } };
环形链表II
我们利用双指针或者哈希集合能够很轻松的检测到是否有循环发生,以及何时退出循环,但是我们该如何返回刚进入循环时的第一个节点呢?
使用哈希集和可以很轻松的解决这个问题
- 哈希集和存储每一个节点
- 遍历到了已存在于哈希集和中的节点,则它就是循环的入口节点,直接返回就好
我们如果使用双指针,该如何获得链表得入口节点呢??
这还不简单,当快指针等于慢指针得时候,定义一个临时得节点,然后让他走到快指针的位置,返回这个位置,不就是这个入口节点吗?
大家仔细想一想,快慢指针的相遇不一定是在入口节点发生的,有可能在循环的圈内,因此返回快指针的位置是完全错误的。。
我们首先来整理一下思路:
- 假设快指针和慢指针在图中圈内的紫色位置相遇。
- 快指针走过的路程:a + n (b + c) + b
- 任意时刻,快指针走的路程一定是慢指针的两倍,快指针走的路程: 2(a + b)
- 因此: a + n (b + c) + b = 2(a + b)
- 得到距离公式:
a=c+(n−1)(b+c)
a=c+(n−1)(b+ c 这个公式是什么意思??
我们把它翻译成人话:
从相遇点到入环点的距离加上 n−1 圈的环长,恰好等于从链表头部到入环点的距离。
因此当一个临时指针和慢指针同时每次走一步,slow从相遇点开始走,pTemp从链表头开始走,最后他们会同时在入口节点处相遇:
class Solution { public: ListNode *detectCycle(ListNode *head) { ListNode* pfast=head; ListNode* pslow=head; while (pfast!=nullptr) { pslow=pslow->next; if (pfast->next==nullptr) { return nullptr; } pfast=pfast->next->next; if (pfast==pslow) { ListNode* pTemp=head; while (pTemp!=pslow) { pTemp=pTemp->next; pslow=pslow->next; } return pTemp; } } return nullptr; } };
本文来自博客园,作者:hugeYlh,转载请注明原文链接:https://www.cnblogs.com/helloylh/p/17209663.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具
· AI 智能体引爆开源社区「GitHub 热点速览」
· C#/.NET/.NET Core技术前沿周刊 | 第 29 期(2025年3.1-3.9)