1. 程式人生 > >求連結串列中倒數第K個結點(Java)

求連結串列中倒數第K個結點(Java)

題目:求連結串列中倒數第K個結點

思路:用兩個指標,第一個指標先走k-1步,然後兩個指標一起走,當第一個指標走到尾節點的時候。第二個指標指向的就是倒數第k個節點。

程式碼:

首先構造連結串列的類(這裡用的是單鏈表):


從上圖可以看出我們我們構造的單鏈表類ListNode中包括兩個屬性:一個屬性為資料data和一個屬性為next指標。

/**
 * 單鏈表結點的類
 * 為了簡化訪問,屬性設定問公有的
 * @author Peter
 */
public class ListNode {
	public int data; // 存放資料的屬性
	public ListNode next; //指向下一個節點
}

實現求連結串列中倒數第k個節點的程式碼方法:

/**
 * 查詢連結串列中倒數第k個結點
 * 
 * @author Peter
 */
public class Main {

	// 查詢連結串列中倒數第k個結點
	public static ListNode CountdownKListNode(ListNode head, int k) {
		// 判斷連結串列是否為空以及k是否為小於0的數
		if (head == null || k < 0) { // 連結串列不能為空,查詢的倒數第k個結點k不能小於0
			return null;
		}
		// 開始時宣告兩個結點使其都指向頭結點
		ListNode aNode = head;
		ListNode bNode = head;
		// 使aNode達到第k個結點
		for (int i = 0; i < k - 1; i++) {
			if (aNode.next != null)
				aNode = aNode.next;
			else {
				return null; // 連結串列太短,打不到k個結點
			}
		}
		while (aNode.next != null) {
			aNode = aNode.next;
			bNode = bNode.next;
		}
		return bNode; // bNode即為倒數第k個結點
	}
}

測試方法

/**
 * 測試求連結串列中倒數第k個結點
 * @author Peter
 *
 */
public class ListNodeTest {

	@Test
	public void testListNodeCountdownK(){
		ListNode ln1 = new ListNode();
		ListNode ln2 = new ListNode();
		ListNode ln3 = new ListNode();
		ListNode ln4 = new ListNode();
		ListNode ln5 = new ListNode();
		ListNode ln6 = new ListNode();
		ListNode ln7 = new ListNode();
		ListNode ln8 = new ListNode();
		ln1.next = ln2;
		ln2.next = ln3;
		ln3.next = ln4;
		ln4.next = ln5;
		ln5.next = ln6;
		ln6.next = ln7;
		ln7.next = ln8;
		ln8.next = null;
		ln1.data = 1;
		ln2.data = 2;
		ln3.data = 3;
		ln4.data = 4;
		ln5.data = 5;
		ln6.data = 6;
		ln7.data = 7;
		ln8.data = 8;

		Main m1 = new Main();
		ListNode kListNode = Main.CountdownKListNode(ln1, 3);
		System.out.println(kListNode.data);

	}
}

思考點:注意這個題設定為單鏈表,我們是用兩個結點,使第一個結點先到達k-1的結點處,然後再使兩個結點一塊走,在第一個結點到達連結串列的尾部,第二個結點所在的位置即為倒數第k個結點的位置。但是如果是雙鏈表的話,我們可以直接從雙鏈表的尾部,直接向頭結點處移動k-1位置即為倒數第k個結點。

拓展:

題目:

求連結串列的中間結點。如果連結串列中結點總數為奇數,返回中間結點;如果結點總數是偶數,返回中間兩個結點的任意一個。

第一思路:

拿到這個題只有有一個O(n^2)的做法,即,先遍歷一遍連結串列,統計出連結串列中結點的個數,然後除以2得到中間結點的位於頭結點後的第幾個結點,然後在遍歷一次,即可得到中間結點。

優化思路:

定義兩個指標,同時從連結串列的頭結點出發,一個指標一次走一步,另一個指標一次走兩步。當走的快的指標走到連結串列末尾的時候,走的慢的指標正好在連結串列的中間。

實現程式碼:

public ListNode getMidNode(ListNode head){
	if(head == null){
		return null;
	}
	ListNode pNode = head; //慢指標
	ListNode qNode = head; //快指標
	while(qNode.next != null){
		if(qNode.next.next != null){
			pNode = pNode.next;
			qNode = qNode.next.next;
		}else{
			pNode = pNode.next;
		}
	}
	return pNode;
}

題目:

判斷一個單向連結串列是否形成了環形結構。

思路:

定義兩個指標,同時從連結串列的頭結點出發,一個指標一次走一步,另一個指標一次走兩步。如果走得快的指標追上了走得慢的指標,那麼連結串列就是環形連結串列;如果走得快的指標走到了連結串列的末尾(m_pNext指向NULL)都沒有追上第一個指標,那麼連結串列就不是環形連結串列。

程式碼實現:

//false為不是環形結構,true為環形結構
public boolean isRingStructure(ListNode head){
	if(head == null){
		return false;
	}
	ListNode pNode = head;
	ListNode qNode = head;
	while(qNode.next!=null){
		if(qNode.next.next != null){
			qNode = qNode.next.next;
		}else{
			return false;
		}
		if(qNode == pNode){
			return true;
		}
		pNode = pNode.next;
	}
	return false;
}

小結:

當用一個指標遍歷連結串列不能解決問題的時候,可以嘗試用兩個指標來遍歷連結串列。可以其中一個指標遍歷的速度快一些(比如一次在連結串列上走兩步),或者讓它先在連結串列上走若干步。