20170515_判断一个单链表是否有环
转自:http://www.cnblogs.com/zhyg6516/archive/2011/03/29/1998831.html
这题目还是慢有意思的。
题目:
0.如何判断单链表里面是否有环?
算法的思想是设定两个指针p, q,其中p每次向前移动一步,q每次向前移动两步。那么如果单链表存在环,则p和q相遇;否则q将首先遇到null。
这里主要理解一个问题,就是为什么当单链表存在环时,p和q一定会相遇呢?
假定单链表的长度为n,并且该单链表是环状的,那么第i次迭代时,p指向元素i mod n,q指向2i mod n。因此当i≡2i(mod n)时,p与q相遇。而i≡2i(mod n) => (2i - i) mod n = 0 => i mod n = 0 => 当i=n时,p与q相遇。这里一个简单的理解是,p和q同时在操场跑步,其中q的速度是p的两倍,当他们两个同时出发时,p跑一圈到达起点,而q此时也刚好跑完两圈到达起点。
那么当p与q起点不同呢?假定第i次迭代时p指向元素i mod n,q指向k+2i mod n,其中0<k<n。那么i≡(2i+k)(mod n) => (i+k) mod n = 0 => 当i=n-k时,p与q相遇。
解决方案:
推广:
1. 如果两个指针的速度不一样,比如p,q,( 0<p<q)二者满足什么样的关系,可以使得两者肯定交与一个节点?
Sp(i) = pi
Sq(i) = k + qi
如果两个要相交于一个节点,则 Sp(i) = Sq(i) => (pi) mod n = ( k+ qi ) mod n =>[ (q -p)i + k ] mod n =0
=> (q-p)i + k = Nn [N 为自然数]
=> i = (Nn -k) /(p-q)
i取自然数,则当 p,q满足上面等式 即 存在一个自然数N,可以满足Nn -k 是 p - q 的倍数时,保证两者相交。
特例:如果q 是p 的步长的两倍,都从同一个起点开始,即 q = 2p , k =0, 那么等式变为: Nn=i: 即可以理解为,当第i次迭代时,i是圈的整数倍时,两者都可以交,交点就是为起点。
2.如何判断单链表的环的长度?
这个比较简单,知道q 已经进入到环里,保存该位置。然后由该位置遍历,当再次碰到该q 位置即可,所迭代的次数就是环的长度。
3. 如何找到链表中第一个在环里的节点?
假设链表长度是L,前半部分长度为k-1,那么第一个再环里的节点是k,环的长度是 n, 那么当q=2p时, 什么时候第一次相交呢?当q指针走到第k个节点时,q指针已经在环的第 k mod n 的位置。即p和q 相差k个元素,从不同的起点开始,则相交的位置为 n-k, 则有了下面的图:
从图上可以明显看到,当p从交点的位置(n-k) ,向前遍历k个节点就到到达环的第一个几点,节点k.
算法就很简单: 一个指针从p和q 中的第一次相交的位置起(n-k),另外一个指针从链表头开始遍历,其交点就是链表中第一个在环里的交点。
4. 如果判断两个单链表有交?第一个交点在哪里?
这个问题画出图,就可以很容易转化为前面的题目。
将其中一个链表中的尾节点与头节点联系起来,则很容发现问题转化为问题3,求有环的链表的第一个在环里的节点。
1、判断是否有环的代码:
//141. Linked List Cycle
//Given a linked list, determine if it has a cycle in it.
//Follow up:
//Can you solve it without using extra space?
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
#include<iostream>
#include<vector>
using namespace std;
struct ListNode
{
int val;
ListNode *next;
ListNode(int x):val(x),next(nullptr) {}
};
ListNode *CreatList(ListNode * &root, vector<int> &a)
{
int n=a.size();
if(n==0)
return root=nullptr;
else
{
root=new ListNode(a[0]);
ListNode *p=root;
for(int i=1; i<n; ++i)
{
ListNode *r=new ListNode(a[i]);
p->next=r;
p=r;
}
return root;
}
}
void OutList(ListNode *root)
{
ListNode *p=root;
if(p==nullptr)
return;
else
{
while(p != nullptr)
{
if(p->next != nullptr)
cout<<p->val<<",";
else
cout<<p->val<<endl;
p=p->next;
}
}
}
class Solution
{
public:
bool hasCycle(ListNode *head) //使用快慢指针来判断单链表是否有环!
{
ListNode *root=head;
if(root == nullptr || root->next == nullptr)
return 0;
else
{
ListNode *fast=root;
ListNode *slow=root;
while( fast != nullptr && fast->next != nullptr)
{
fast=fast->next->next;
slow=slow->next;
if(fast == slow) //快慢指针相遇,有环。
return 1;
}
if(fast == nullptr || fast->next == nullptr) //快指针到达 nullptr,说明没有相遇,没有环。
return 0;
}
}
};
int main()
{
vector<int> a;
int ch;
cout<<"依次输入单链表节点数据:"<<endl;
while(cin>>ch)
a.push_back(ch);
cin.clear();
cin.sync();
ListNode *r;
ListNode *root=CreatList(r,a);
cout<<"单链表建立完成."<<endl;
cout<<"顺序输出单链表:"<<endl;
OutList(root);
cout<<"顺序输出单链表完成."<<endl;
Solution example;
bool result;
result=example.hasCycle(root);
cout<<"该单链表有环吗?"<<result<<endl;
system("pause");
return 0;
}
2、寻找环的入口处的代码:
//142. Linked List Cycle II
// Given a linked list, return the node where the cycle begins. If there is no cycle, return null.
//Note: Do not modify the linked list.
//Follow up:
//Can you solve it without using extra space?
//寻找环的入口
/**
* Definition for singly-linked list.
* struct ListNode {
* int val;
* ListNode *next;
* ListNode(int x) : val(x), next(NULL) {}
* };
*/
#include<iostream>
#include<vector>
using namespace std;
struct ListNode
{
int val;
ListNode *next;
ListNode(int x):val(x),next(nullptr) {}
};
ListNode *CreatList(ListNode * &root, vector<int> &a)
{
int n=a.size();
if(n==0)
return root=nullptr;
else
{
root=new ListNode(a[0]);
ListNode *p=root;
for(int i=1; i<n; ++i)
{
ListNode *r=new ListNode(a[i]);
p->next=r;
p=r;
}
return root;
}
}
void OutList(ListNode *root)
{
ListNode *p=root;
if(p==nullptr)
return;
else
{
while(p != nullptr)
{
if(p->next != nullptr)
cout<<p->val<<",";
else
cout<<p->val<<endl;
p=p->next;
}
}
}
class Solution
{
public:
ListNode *detectCycle(ListNode *head)
{
ListNode *root=head;
if(root == nullptr || root->next == nullptr) //无节点或者只有一个节点时,没有环:
return nullptr;
else
{
ListNode *fast=root; //使用快慢指针来判断单链表是否有环!
ListNode *slow=root;
while( fast != nullptr && fast->next != nullptr)
{
fast=fast->next->next;
slow=slow->next;
if(fast == slow) //快慢指针相遇,有环:
{
fast=root; //fast指针指向X处,slow指针指向Z处,
//接下来,这两个指针每次向前走一步,则会在Y处相遇,即环的入口处。
while(fast != slow)
{
fast=fast->next;
slow=slow->next;
}
if(fast == slow)
return fast; //环的入口处。
}
}
if(fast == nullptr || fast->next == nullptr) //快指针到达 nullptr,说明没有相遇,没有环:
return nullptr;
}
}
};
int main()
{
vector<int> a;
int ch;
cout<<"依次输入单链表节点数据:"<<endl;
while(cin>>ch)
a.push_back(ch);
cin.clear();
cin.sync();
ListNode *r;
ListNode *root=CreatList(r,a);
cout<<"单链表建立完成."<<endl;
cout<<"顺序输出单链表:"<<endl;
OutList(root);
cout<<"顺序输出单链表完成."<<endl;
Solution example;
ListNode *result;
result=example.detectCycle(root);
system("pause");
return 0;
}
题目1:给定一个链表,判断它是否有环。
例如:给出 -21->10->4->5, tail connects to node index 1,返回 true。
挑战:不要使用额外指针。
思路:使用两个指针,初始时两个指针均指向链表头位置,然后一个指针每次走两步,一个指针每次走一步,如果在循环过程中遇到两个指针相等,则说明有循环返回 true。如果出现一个指针无法继续往下走,则退出循环返回 false。
思路的证明:因为 fast 先进入环,在 slow 进入之后,如果把 slow 看作在前面,fast在后面每次循环都向 slow 靠近1,所以一定会相遇,而不会出现 fast 直接跳过 slow 的情况。
public class Solution{
public Boolean hasCycle(ListNode head){
if(head == null || head.next == null){
return false;
}
ListNode fast, slow;
fast = head.next;
slow = head;
while (fast != slow) {
if(fast==null || fast.next==null)
return false;
fast = fast.next.next;
slow = slow.next;
}
return true;
}
}
题目2:环的长度是多少?
方法一:第一次相遇后,让fast停着不走了,slow继续走,记录到下次相遇时循环了几次。
方法二:
第一次相遇时slow走过的距离:a+b,fast走过的距离:a+b+c+b。因为fast的速度是slow的两倍,所以fast走的距离是slow的两倍,有 2(a+b) = a+b+c+b,可以得到a=c。我们发现L=b+c=a+b,也就是说,从一开始到二者第一次相遇,循环的次数就等于环的长度。
题目3:如何找到环中第一个节点(即Linked List Cycle II)?
方法:我们已经得到了结论a=c,那么让两个指针分别从X和Z开始走,每次走一步,那么正好会在Y相遇!也就是环的第一个节点。
public class Solution {
public ListNode detectCycle(ListNode head) {
if (head == null || head.next==null) {
return null;
}
ListNode fast, slow;
fast = head.next;
slow = head;
while (fast != slow) {
if(fast==null || fast.next==null)
return null;
fast = fast.next.next;
slow = slow.next;
}
while (head != slow.next) {
head = head.next;
slow = slow.next;
}
return head;
}
}
题目4:如何将有环的链表变成单链表(解除环)?
方法:在上一个问题的最后,将c段中Y点之前的那个节点与Y的链接切断即可。
题目5: 如何判断两个单链表是否有交点?如何找到第一个相交的节点?
方法:先判断两个链表是否有环,如果一个有环一个没环,肯定不相交;如果两个都没有环,判断两个列表的尾部是否相等;如果两个都有环,判断一个链表上的Z点是否在另一个链表上。