链表中关于环的几个小问题

参考很多资料,也做了点题目,在这里小汇总一下。有不到位的地方,恳请指出,谢谢!


描述:

给定一个链表头指针为head,针对这个链表有如下问题:

1、如何判断这个链表有没有环?

2、环的长度如何计算?

3、怎么找到环开始的地方?


解决思路:

1、两种思路,第一种:用一个map保存节点的地址,从前往后扫,如果某个节点的地址出现了两次,则说明有环。这种思路实现起来也很快,但不适合解决第二个问题,空间复杂度比较大。在这里不做主要讨论。如果笔试或者面试碰到,一般考的是这里的第二种方法。取两个指针,slow和fast,开始都指向head,slow每次走一步,fast每次走两步,如果这两个指针最后能够相遇,则说明有环。它的一个简单实现,参考:http://blog.csdn.net/hale1007/article/details/22226133

2、两个指针在环内相遇之后,再继续走,到第二次再相遇,慢指针走了一圈,快指针走了两圈,用一个计数器记住慢指针的走的步数就可以得到环的长度。

3、如下图:


slow和fast在p的位置相遇,这个时候slow走的距离 s = N + M2,fast走的距离为2s,因为二者相遇,肯定有fast在环内打转,假设fast转了n圈(其中n>=1),

所以就有 2s = N + n(M1 + M2) + M2;综合这两个等式,于是就有,N + M2 = n(M1 + M2),也就是N = (n-1)(M1+ M2) + M1

有了这个结论,让slow和fast在p相遇之后,让fast指向链表开头,从这个时候开始fast每次只走一步,slow继续在环内走,还是一次一步,两个指针再次相遇的地方就是环开始的地方,注意这个时候,slow在环内走了n-1圈。LeetCode上有这个题目,参考:http://blog.csdn.net/hale1007/article/details/22225483


一个完整实现的代码:

#include <iostream>

using namespace std;

struct ListNode
{
	int data;
	ListNode *next;
};

ListNode *build_list_with_cycle(int a[], int len, int begin)	// begin为环开始的地方
{
	ListNode *head = nullptr, *p = nullptr, *q = nullptr;	// 用q记录begin指向的位置,即环开始的地方
	for (int i = 0; i < len; i++)
	{
		ListNode *tmp = new ListNode();
		tmp->data = a[i];
		if (i == 0)
		{
			head = p = tmp;
			continue;
		}
		if (i == begin)
			q = tmp;
		p->next = tmp;
		p = p->next;
	}
	p->next = q;	// 最后一个节点指针指向之前记录的begin 的位置,让链表成环
	return head;
}

bool has_cycle(ListNode *head)
{
	ListNode *fast = head, *slow = head;	// 快慢指针开始都指向第一个节点
	while (slow && fast && fast->next)
	{
		fast = fast->next->next;			// 快的每次走两步,慢的每次走一步
		slow = slow->next;
		if (slow == fast)					// 快慢指针相遇,则说明有环
			return true;
	}
	return false;
}

int cycle_length(ListNode *head)	// 在已经确定有环的情况下求环的长度
{
	ListNode *fast = head, *slow = head;
	int length = 1;					// 环长度至少为1
	while (fast && fast->next)
	{
		fast = fast->next->next;
		slow = slow->next;
		if (fast == slow)
		{
			fast = fast->next->next;
			slow = slow->next;
			while (fast != slow)
			{
				length++;			// slow每走一步,长度加1
				fast = fast->next->next;
				slow = slow->next;
			}
			break;
		}
	}
	return length;
}

ListNode *find_cycle_begin(ListNode *head)	//如果有环,返回环开始的地方,如果没有,返回nullptr
{
	ListNode *fast = head, *slow = head;
	while (fast && fast->next)
	{
		fast = fast->next->next;
		slow = slow->next;
		if (fast == slow)
		{
			fast = head;
			while (fast != slow)
			{
				fast = fast->next;
				slow = slow->next;
			}
			return slow;		// or return fast
		}
	}
	return nullptr;			// 找不到环,则返回空
}


int main()
{
	int a[] = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12 };
	int len = sizeof(a) / sizeof(int);
	int begin;
	cin >> begin;
	ListNode *head = build_list_with_cycle(a, len, begin);
	ListNode *cycle_begin = nullptr;
	int pre_length;
	if (has_cycle(head))
	{
		cout << "has cycle" << endl;
		cout << "cycle length: " << cycle_length(head) << endl;
		cycle_begin = find_cycle_begin(head);
		cout << "the data of the cycle begin " << cycle_begin->data << endl;
	}
	else
		cout << "no cycle" << endl;
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值