1. 程式人生 > >C語言實現單鏈表面試題--進階(帶環問題)

C語言實現單鏈表面試題--進階(帶環問題)

1.判斷單鏈表是否帶環?若帶環,求環的長度?求環的入口點?
2.判斷兩個連結串列是否相交,若相交,求交點。(假設連結串列不帶環)

3.判斷兩個連結串列是否相交,若相交,求交點。(假設連結串列可能帶環)【升級版】

函式如下:

typedef int DataType;

typedef struct ListNode
{
	DataType data;
	ListNode* next;
}ListNode;
int GetCycleLen(ListNode* plist);				//求環長度(若無環則返回0)
ListNode* IsCycle(ListNode* plist);				//求帶環單鏈錶快慢指標相遇點(無環返回NULL)
ListNode* GetCycleEntry(ListNode* plist);			//求帶環單鏈表入口點(無環返回NULL)

ListNode* IsCrossNoCycle(ListNode* plist1, ListNode* plist2);	//判斷2個無環連結串列是否相交
ListNode* IsCross(ListNode* plist1, ListNode* plist2);		//判定2個連結串列是否相交(分類討論)


1.判斷是否帶環

思路:快慢指標,若能相遇則帶環

ListNode* IsCycle(ListNode* plist)
{
	ListNode* fast = plist, *slow = plist; //注意這種定義形式
	
	while(fast && fast->next)
	{
		fast = fast->next->next;
		slow = slow->next;
		if(fast == slow)
		{
			return slow;
		}
	}

	return NULL;
}
2.求環的長度

思路:從快慢指標相遇點走一圈,用計數器計數

int GetCycleLen(ListNode* plist)
{
	//先判斷是否為帶環單鏈表,不是的話返回0
	ListNode* meet = IsCycle(plist);
	
	if(meet)
	{
		int count = 1;
		ListNode* cur = meet->next;
		while(meet != cur)
		{
			cur = cur->next;
			count++;
		}
		return count;
	}
	
	//若meet為空(沒有環),返回0
	else
	{
		return 0;
	}
}


3.判斷入口點

思路:設頭結點到入口點距離L,入口點到相遇點距離X,環長度C
    因為快指標是慢指標的兩倍,且在相遇點相遇,所以 2(L+X) = L+X+C*n(快指標領先圈數)
    解得L=n*C-X
    所以從快慢指標相遇點和頭結點一起走,他們的相遇點就是入口點

ListNode* GetCycleEntry(ListNode* plist)
{
	ListNode* meet = IsCycle(plist);
	if(meet)
	{
		while (meet != plist)
		{
			meet = meet->next;
			plist = plist->next;
		}
		return plist;
	}
	else
	{
		return NULL;
	}
}
4.無環連結串列相交

思路:計算兩個連結串列的長度差的絕對值gab,長的連結串列先移動gab步,然後兩個連結串列一起走。如果在走到結尾之前相遇了,則該相遇點就是交點

ListNode* IsCrossNoCycle(ListNode* plist1, ListNode* plist2)
{
	//plist1和plist2都是無環連結串列則進入迴圈,且plist1和plist2不為空
	if(IsCycle(plist1) == NULL && IsCycle(plist2) == NULL && plist1 && plist2)
	{
		ListNode* cur1 = plist1, *cur2 = plist2;
		
		//求plist1和plist2的長度
		int len1 = 0;
		int len2 = 0;
		while (cur1)
		{
			len1++;
			cur1 = cur1->next;
		}
		while (cur2)
		{
			len2++;
			cur2 = cur2->next;
		}
		
		//移動較長的連結串列的指標,讓他和短的連結串列一樣長
		int gap = abs(len1 - len2);
		if (len1 > len2)
		{
			while(gap--)
			{
				plist1 = plist1->next;
			}
		}
		else
		{
			while(gap--)
			{
				plist2 = plist2->next;
			}
		}
		
		//一起走,如果中途相等了則有交點
		while (plist1)
		{
			if(plist1 == plist2)
			{
				return plist1;
			}
			plist1 = plist1->next;
			plist2 = plist2->next;
		}
	}
	return NULL;
}
5.任意兩個連結串列判斷相交:

1) 兩個都不帶環;

2) 其中一個帶環;

3) 兩個都帶環

ListNode* IsCross(ListNode* plist1, ListNode* plist2)
{
	ListNode* ent1 = GetCycleEntry(plist1);
	ListNode* ent2 = GetCycleEntry(plist2);
	
	//1.兩個都不帶環,轉化成無環相交問題
	if (ent1 == NULL && ent2 == NULL)
	{
		return IsCrossNoCycle(plist1, plist2);
	}

	//2.其中一個帶環,必定不相交
	else if ((ent1 == NULL && ent2) || (ent2 == NULL && ent1))
	{
		return NULL;
	}

	//3.兩個都帶環
	//	1)不相交
	//	2)尾交
	//	3)環交
	
	else
	{
		//1)若入口點相同則尾交,去掉環,轉化成無環相交問題
		if(ent1 == ent2)
		{
			ent1->next = NULL;
			return IsCrossNoCycle(plist1, plist2);
		}

		else
		{
			//2)同環,兩個入口點
			//  解決方案:一個入口點開始遍歷一圈,看環上是否有另一個入口點
			//  若找到則返回plist1的入口點
			ListNode* cur = ent1->next;
			while (cur != ent1)
			{
				if(cur == ent2)
				{
					return ent1;
				}
				cur = cur->next;
			}
			return NULL;	//不相交
		}
	}
}