20170515_判断一个单链表是否有环

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点是否在另一个链表上。








评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值