【劍指offer】面試題18:刪除連結串列的節點
目錄:
題目一:O(1)時間內刪除一個節點
題目1:給定單向連結串列的頭指標和一個節點指標,定義一個函式在O(1)時間刪除該節點。
在單向連結串列中刪除一個節點,最常規的方法無疑是從連結串列的頭結點開始,順序遍歷查詢要刪除的節點,並在連結串列中刪除該節點。
比如下圖 a 所表示的連結串列中,我們要刪除節點 i,可以從連結串列的頭節點 a 開始順序遍歷,發現節點 h 的 next 指向要刪除的節點i,於是我們可疑把節點h的 next 指向i的下一個節點即為 j。指標調整之後,我們就可以安全地刪除節點 i 並保證連結串列沒有斷開。這種思路由於需要順序查詢,時間複雜度自然就是O(n)了。
之所以要從頭開始查詢,是因為我們需要得到被刪除的節點的前面一個節點。在單向連結串列中,節點中沒有前一個節點的指標,所以只好從連結串列的頭結點開始順序查詢。
那是不是一定要得到被刪除的節點的前一個節點呢?答案是否定的。我們可以很方面地得到要刪除的節點的下一個節點。如果我們把下一個節點的內容複製到要刪除的節點上覆蓋原有的內容,再把下一個節點刪除,那是不是就相當於把當前要刪除的節點刪除了?
我們還是以前面的例子來分析這種思路,我們要刪除的節點 i,先把i的下一個節點 j 的內容複製到 i,然後把 i 的指標指向節點 j 的下一個節點。此時再刪除節點 j,其效果剛好是把節點 i 給刪除了(圖c)
上述問題還有一個問題:如果要刪除的節點位於連結串列的尾部,那麼它就沒有下一個節點,怎麼辦?我們仍然從連結串列的頭節點開始,順序遍歷得到該節點的前序節點,並完成刪除操作。
最後需要注意的是,如果連結串列中只有一個節點,而我們又要 連結串列的頭節點(也是尾節點),此時我們在刪除節點之後,還需要把連結串列的頭節點設定為null。
有了這種思路,現在我們是實現程式碼:
public class DeleteNodeInO1 { class Node{ private int data; private Node next; public Node(int data){ this.data = data; } } public Node deleteNode(Node head, Node delNode){ if(head == null || delNode == null){ return null; } // 1.要刪除的節點不是尾節點 if(delNode.next != null){ Node node = delNode.next; delNode.data = node.data; // 內容覆蓋 delNode.next = node.next; // delNode指標指向它下下個節點 } // 2.連結串列只有一個節點,刪除頭節點,也是尾節點 else if(head == delNode && head.next == null){ delNode = null; head = null; return null; } // 3.連結串列中有多個節點,要刪除的節點是尾節點 else{ // 從頭節點開始遍歷,找到待刪除節點的前一個節點 Node node = head; while(node.next != delNode){ node = node.next; } node.next = null; delNode = null; } return head; } // 列印連結串列 public void print(Node head){ if(head == null){ System.out.println("連結串列為空!"); } while(head != null){ System.out.print(head.data + " "); head = head.next; } System.out.println(); } // 測試 public static void main(String[] args) { DeleteNodeInO1 dn = new DeleteNodeInO1(); Node head = null; dn.print(dn.deleteNode(head, head)); // 連結串列為空! // 只有一個節點,刪除尾結點也是頭節點 head = dn.new Node(0); Node p = head; dn.print(dn.deleteNode(head, head)); // 連結串列為空! for (int i = 0; i < 5; i++) { Node node = dn.new Node(i); p.next = node; p = p.next; } Node tail = dn.new Node(5); p.next = tail; dn.print(head); // 0 0 1 2 3 4 5 dn.print(dn.deleteNode(head, head)); // 0 1 2 3 4 5 // 連結串列中有多個節點,刪除尾節點 dn.print(dn.deleteNode(head, tail)); // 0 1 2 3 4 // 刪除的不是尾節點 dn.print(dn.deleteNode(head, head.next)); // 0 2 3 4 } }
題目二:刪除連結串列中重複的節點
題目2:刪除連結串列中重複的節點。例如在一個排序的連結串列中,存在重複的結點,請刪除該連結串列中重複的結點,重複的結點不保留,返回連結串列頭指標。 例如,連結串列1->2->3->3->4->4->5 處理後為 1->2->5。
public class DeleteRepeatNode {
class Node{
private int data;
private Node next;
public Node(int data){
this.data = data;
}
}
public Node deleteDuplication(Node head){
// 只有0或1個節點時,則返回頭節點
if(head == null || head.next == null){
return head;
}
// 當前節點是重複節點
if(head.data == head.next.data){
Node node = head.next; // 當前節點的下一個節點
while(node != null && node.data == head.data){
// 跳過值與當前結點相同的全部結點,找到第1個與當前結點不同的結點
node = node.next; // 這個過程其實將中間值相同的都刪除了
}
// 從第一個與當前結點不同的結點開始遞迴
return deleteDuplication(node);
}else{
// 當前節點不是重複節點,則保留當前結點,從下一個結點開始遞迴
head.next = deleteDuplication(head.next);
return head;
}
}
}
程式碼中的巧妙之處在於這個while迴圈中以及後面的遞迴過程:
while(node != null && node.data == head.data){
node = node.next;
}
while迴圈中找到值相同的節點後,繼續迴圈,直到找到值與當前節點不同的節點,則直接將其設定為node,這就意味著中間值相等的哪些節點直接就被刪除了。這種遞迴的解法思路很清晰。