求連結串列中倒數第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;
}
小結:
當用一個指標遍歷連結串列不能解決問題的時候,可以嘗試用兩個指標來遍歷連結串列。可以其中一個指標遍歷的速度快一些(比如一次在連結串列上走兩步),或者讓它先在連結串列上走若干步。