1. 程式人生 > >劍指Offer 面試題23:連結串列中環的入口節點 Java程式碼實現

劍指Offer 面試題23:連結串列中環的入口節點 Java程式碼實現

題目描述

一個連結串列中包含環,請找出該連結串列的環的入口結點。   這題又用到了一快一慢兩個指標的方法,快指標一次走兩步慢指標一次走一步。可以證明,若存在環路,則這兩個指標一定會在環路中某個地方相遇。這也是檢測一個連結串列是否存在環路的方式。   接下來具體分析一下這兩個指標什麼時候會相遇。假設從第一個資料節點到環的入口節點需要走k步,那麼當慢指標slow走到環的入口節點時,快指標fast已經走了2k步,其中在環內走了k步。k可能會比環路的節點數大會很多,快指標應該距離環的入口節點mod(k,LOOP_SIZE)步,記為K。 也就是說:當slow走到入口節點時,fast在環內走了K步。slow落後於fast,相距K。或者說fast落後slow,相距(LOOP_SIZE-K)步。
每走一次,fast都會靠近slow步。於是兩者將在(LOOP_SIZE-K)步之後相遇,這個相遇點就距離環入口 K 步。   下面說一下怎麼找到環路起始處。上面說了,在相遇點,還需走K步到環的入口節點 。 因為K=mod(k,LOOP_SIZE), k=K+M*LOOP_SIZE,所以也可以說從碰撞處,需要走k步到達環的入口節點(就是在環裡面多繞幾圈)。也就是說,連結串列第一個資料節點和碰撞節點都需要走k步到達環的入口節點。碰撞後,我們把一個指標指向連結串列第一個節點,一個指標指向碰撞處,以同樣的速度移動,他們會在環的入口處相遇。 實現的Java程式碼如下(這兒的連結串列包含一個沒有資料域的頭結點):
public static ListNode entryNodeOfLoop2(ListNode head){
		if(head==null||head.next==null)
			return null;
		
		ListNode slow=head.next,fast=head.next;
		
		//找到碰撞處 ,處於連結串列中LOOPSIZE-k步
		while(fast!=null&&fast.next!=null){
			slow=slow.next;
			fast=fast.next.next;
			if(fast==slow)
				break;
		}
		//錯誤檢查 沒有環路
		if(fast==null||fast.next==null){
			return null;
		}
		//發生碰撞後,再將slow指向連結串列第一個資料點,fast流在碰撞處
		//同速度走,相碰的地方就是 環路起始處
		slow=head.next;
		while(slow!=fast){
			slow=slow.next;
			fast=fast.next;
		}
		return fast;
	}
劍指Offer上的實現,有些許不同。上面的的分析比較全面,分析後代碼也更簡潔。Offer中的方法更好理解,但是程式碼量稍多一點。各有優點,大家自己斟酌。Offer中的實現思路如下: 1.通過一快一慢兩個指標找到碰撞處 2.遍歷環,得到環中的節點數N 3.兩個指標都初始指向第一個節點,然後第一個指標先走N步(兩個指標相距就是環內節點數),然後一起走,第一次相遇的點就是環的入口。
public static ListNode meetingNode(ListNode head){
		if(head==null||head.next==null)
			return null;
		
		ListNode slow=head.next,fast=head.next;
		
		while(fast!=null&&fast.next!=null){
			slow=slow.next;
			fast=fast.next.next;
			if(fast==slow)
				return fast;
		}
		return null;
	}
	
	public static ListNode entryNodeOfLoop(ListNode head){
		ListNode meetingNode=meetingNode(head);
		if(meetingNode==null) return null;
		
		//求環節點數目
		int loopCount=1;
		ListNode pNode=meetingNode;
		while(pNode.next!=meetingNode){
			pNode=pNode.next;
			loopCount++;
		}
		//從第一個資料節點開始移動loopCount次
		pNode=head.next;
		for(int i=0;i<loopCount;i++){
			pNode=pNode.next;
		}
		//同時移動
		ListNode pNode2=head.next;
		while(pNode!=pNode2){
			pNode=pNode.next;
			pNode2=pNode2.next;
		}
		
		return pNode;
	}