本文正在參加「Python主題月」,詳情查看 活動連接markdown
這是「牛客網」上的「JZ 56 刪除鏈表中重複的結點」,難度爲「較難」 。函數
Tag : 「劍指 Offer」、「鏈表」、「單鏈表」post
在一個排序的鏈表中,存在重複的結點,請刪除該鏈表中重複的結點,重複的結點不保留,返回鏈表頭指針。ui
例如,鏈表 1->2->3->3->4->4->5
處理後爲 1->2->5
spa
示例 1:設計
輸入:{1,2,3,3,4,4,5}
返回值:{1,2,5}
複製代碼
要求:指針
時間:1 scode
空間:64 Morm
首先一個比較「直觀且通用」的思路是,採用「邊遍歷邊構造」的方式:排序
建一個「虛擬頭節點」dummy
以減小邊界判斷,日後的答案鏈表會接在 dummy
後面;
使用 tail
表明當前有效鏈表的結尾;
經過原輸入的 pHead
指針進行鏈表掃描。
對原鏈表進行遍歷,只要原鏈表還沒有到達結尾,咱們就重複以下決策(保留/跳過邏輯):
保留:pHead
已經沒有下一個節點,pHead
能夠被保留(插入到答案結尾指針 tail
後面);pHead
有一下個節點,可是值與 pHead
不相同,pHead
能夠被保留;
跳過:當發現 pHead
與下一個節點值相同,須要對「連續相同一段」進行跳過。
舉個 🌰,以題目示例 [1,2,3,3,4,4,5]
爲例,使用圖解的方式來感覺一下。
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
複製代碼
遞歸解法相比於迭代解法,代碼要簡潔一些,但思惟難度要高一些。
首先不管是否爲「鏈表」類的題目,在實現遞歸前,都須要先明確「咱們指望遞歸函數完成什麼功能」,即設計好咱們的遞歸函數簽名。
顯然,咱們但願存在一個遞歸函數:傳入鏈表頭結點,對傳入鏈表的重複元素進行刪除,返回操做後的鏈表頭結點。
該功能與題目要咱們實現的 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 == null) return 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)
複製代碼
本質沒有改變,只須要抓住「遍歷過程當中,節點什麼時候可以被保留」便可。
Java 代碼:
class Solution {
public ListNode deleteDuplication(ListNode head) {
if (head == null) return 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」中比較經典而又不過期的題目都講一遍。
在提供追求「證實」&「思路」的同時,提供最爲簡潔的代碼。
歡迎關注,交個朋友 (`・ω・´)