【劍指 の 精選】詳解「刪除鏈表中重複結點」的兩種解法 |Python 主題月

本文正在參加「Python主題月」,詳情查看 活動連接markdown

題目描述

這是「牛客網」上的「JZ 56 刪除鏈表中重複的結點」,難度爲「較難」 。函數

Tag : 「劍指 Offer」、「鏈表」、「單鏈表」post

在一個排序的鏈表中,存在重複的結點,請刪除該鏈表中重複的結點,重複的結點不保留,返回鏈表頭指針。ui

例如,鏈表 1->2->3->3->4->4->5 處理後爲 1->2->5spa

示例 1:設計

輸入:{1,2,3,3,4,4,5}

返回值:{1,2,5}

複製代碼

要求:指針

  • 時間:1 scode

  • 空間:64 Morm

迭代解法

首先一個比較「直觀且通用」的思路是,採用「邊遍歷邊構造」的方式:排序

  1. 建一個「虛擬頭節點」dummy 以減小邊界判斷,日後的答案鏈表會接在 dummy 後面;

  2. 使用 tail 表明當前有效鏈表的結尾;

  3. 經過原輸入的 pHead 指針進行鏈表掃描。

對原鏈表進行遍歷,只要原鏈表還沒有到達結尾,咱們就重複以下決策(保留/跳過邏輯):

  • 保留:pHead 已經沒有下一個節點,pHead 能夠被保留(插入到答案結尾指針 tail 後面);pHead 有一下個節點,可是值與 pHead 不相同,pHead 能夠被保留;

  • 跳過:當發現 pHead 與下一個節點值相同,須要對「連續相同一段」進行跳過。

舉個 🌰,以題目示例 [1,2,3,3,4,4,5] 爲例,使用圖解的方式來感覺一下。

  1. 「當前節點」與「下一節點」值不一樣,當前節點進行保留:

圖片

  1. 「當前節點」與「下一節點」值相同,跳過「相同的連續一段」,當前節點不能保留:

圖片

Java 代碼:

class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        ListNode dummy = new ListNode(-1);
        ListNode tail = dummy;
        while (pHead != null) {
            // 進入循環時,確保了 pHead 不會與上一節點相同
            if (pHead.next == null || pHead.next.val != pHead.val) {
                tail.next = pHead;
                tail = pHead;
            }
            // 若是 pHead 與下一節點相同,跳過相同節點(到達「連續相同一段」的最後一位)
            while (pHead.next != null && pHead.val == pHead.next.val) pHead = pHead.next;
            pHead = pHead.next;
        }
        tail.next = null;
        return dummy.next;
    }
}
複製代碼

Python 3 代碼:

class Solution:
    def deleteDuplication(self, pHead):
        dummy = ListNode(-1)
        tail = dummy
        while pHead is not None:
            # 進入循環時,確保了 pHead 不會與上一節點相同
            if pHead.next is None or pHead.next.val != pHead.val:
                tail.next = pHead
                tail = pHead
            # 若是 pHead 與下一節點相同,跳過相同節點(到達「連續相同一段」的最後一位)
            while pHead.next is not None and pHead.val == pHead.next.val:
                pHead = pHead.next
            pHead = pHead.next
        tail.next = None
        return dummy.next
複製代碼
  • 時間複雜度:O(n)
  • 空間複雜度:O(n)

遞歸解法

遞歸解法相比於迭代解法,代碼要簡潔一些,但思惟難度要高一些。

首先不管是否爲「鏈表」類的題目,在實現遞歸前,都須要先明確「咱們指望遞歸函數完成什麼功能」,即設計好咱們的遞歸函數簽名。

顯然,咱們但願存在一個遞歸函數:傳入鏈表頭結點,對傳入鏈表的重複元素進行刪除,返回操做後的鏈表頭結點。

該功能與題目要咱們實現的 deleteDuplication 函數相同,所以咱們直接使用原函數做爲遞歸函數便可。

以後再考慮「遞歸出口」和「遞歸環節的最小操做」:

  • 遞歸出口:考慮什麼狀況下,咱們再也不須要「刪除」操做。顯然當傳入的參數 pHead 爲空,或者 pHead.next 爲空時,必然不存在重複元素,可直接返回 pHead

  • 遞歸環節的最小操做:以後再考慮刪除邏輯該如何進行:

  • 顯然,當 pHead.val != pHead.next.val  時,pHead 是能夠被保留的,所以咱們只須要將 pHead.next 傳入遞歸函數,並將返回值做爲 pHead.next,而後返回 pHead 便可;

  • pHead.val == pHead.next.val 時,pHead 不能被保留,咱們須要使用臨時變量 tmp 跳過「與 pHead.val 值相同的連續一段」,將 tmp 傳入遞歸函數所得的結果做爲本次返回。

Java 代碼:

public class Solution {
    public ListNode deleteDuplication(ListNode pHead) {
        // 遞歸出口:當「輸入節點爲空」或者「不存在下一節點」,直接返回
        if (pHead == null || pHead.next == nullreturn pHead;
        
        if (pHead.val != pHead.next.val) {
            // 若「當前節點」與「下一節點」值不一樣,則當前節點能夠被保留
            pHead.next = deleteDuplication(pHead.next);
            return pHead;
        } else {
            // 若「當前節點」與「下一節點」相同,須要跳過「值相同的連續一段」
            ListNode tmp = pHead;
            while (tmp != null && tmp.val == pHead.val) tmp = tmp.next;
            return deleteDuplication(tmp);
        }
    }
}
複製代碼

Python 3 代碼:

class Solution:
    def deleteDuplication(self, pHead):
        # 遞歸出口:當「輸入節點爲空」或者「不存在下一節點」,直接返回
        if pHead is None or pHead.next is None:
            return pHead
        if pHead.val != pHead.next.val:
            # 若「當前節點」與「下一節點」值不一樣,則當前節點能夠被保留
            pHead.next = self.deleteDuplication(pHead.next)
            return pHead
        else:
            # 若「當前節點」與「下一節點」相同,須要跳過「值相同的連續一段」
            tmp = pHead
            while tmp is not None and tmp.val == pHead.val:
                tmp = tmp.next
            return self.deleteDuplication(tmp)
複製代碼
  • 時間複雜度:O(n)
  • 空間複雜度:忽略遞歸帶來的額外空間開銷,複雜度爲 O(1)

拓展

  • 若是問題變爲「相同節點保留一個」,該如何實現?

本質沒有改變,只須要抓住「遍歷過程當中,節點什麼時候可以被保留」便可。

Java 代碼:

class Solution {
    public ListNode deleteDuplication(ListNode head) {
        if (head == nullreturn head;
        ListNode dummy = new ListNode(-109);
        ListNode tail = dummy;
        while (head != null) {
            // 值不相等才追加,確保了相同的節點只有第一個會被添加到答案
            if (tail.val != head.val) {
                tail.next = head;
                tail = tail.next;
            }
            head = head.next;
        }
        tail.next = null;
        return dummy.next;
    }   
}
複製代碼

Python 3 代碼:

class Solution:
    def deleteDuplication(self, pHead):
        if pHead is None:
            return pHead
        dummy = ListNode(-109)
        tail = dummy
        while pHead is not None:
            # 值不相等才追加,確保了相同的節點只有第一個會被添加到答案
            if tail.val != pHead.val:
                tail.next = pHead
                tail = tail.next
            pHead = pHead.next
        tail.next = None
        return dummy.next
複製代碼
  • 時間複雜度:

  • 空間複雜度:

最後

這是咱們「劍指 の 精選」系列文章的第 No.56 篇,系列開始於 2021/07/01。

該系列會將「劍指 Offer」中比較經典而又不過期的題目都講一遍。

在提供追求「證實」&「思路」的同時,提供最爲簡潔的代碼。

歡迎關注,交個朋友 (`・ω・´)

相關文章
相關標籤/搜索