前言
最近在刷《劍指offer》的題,其中有一道題目叫做刪除鏈表中重復的節點,我想了半天沒想到比較好的解決辦法,於是看了看大佬的解析(菜哭了)。不看不知道,一看嚇一跳,這尼瑪寫的也太妙了,忍不住寫篇博客記錄一下這個解題思路和代碼。
題目描述
在一個排好序的鏈表中,存在重復的結點,請刪除該鏈表中重復的結點,重復的結點不保留,返回鏈表頭指針。 例如,鏈表1->2->3->3->4->4->5
處理后為 1->2->5
。
解題思路
這道題我們分兩種情況來考慮:
- 首先第一種情況:頭節點的值存在重復;比如
1->1->1->2->3->3->4
,前面這個鏈表的頭節點重復了3次
,所以這時候,我們應該舍棄前3個重復的節點1,將2
作為新的頭節點,再繼續向后判斷; - 第二種情況就是頭節點並不與它的下一個節點重復;比如上面的這個鏈表,我們去除了前面的
3個1
之后,剩下2->3->3->4
,這時候,頭節點不與后面的節點重復了,那我們保留頭節點,並繼續向后判斷,發現后面后面的兩個3
發生了重復,於是,我們去除這個兩個節點,並讓原來頭節點的next指向去除重復后的下一個位置,也就變成了2->4
;若4后面還有其他重復,則我們去除重復后,讓4指向剩下的部分;
代碼實現
其實上面的思路並不是很難想到,關鍵是代碼如何實現呢?下面這個代碼就是大佬對於上面這個思路的實現:
public ListNode deleteDuplication(ListNode pHead) {
// 若頭節點為空,或者鏈表只有一個節點,則必沒有重復,值將返回
if(pHead == null || pHead.next == null) {
return pHead;
}
// 保存頭節點的下一個節點,上面已經判斷了pHead.next不是空
ListNode next = pHead.next;
// 若頭節點的值與下一個節點的值相同
if(pHead.val == next.val) {
do{
// 則繼續向前找出與頭節點重復的節點
// 直到找到第一個與頭節點不同的節點后,退出循環
next = next.next;
}while(next != null && next.val == pHead.val);
// 舍棄前面的所有重復節點,將當前第一個與頭節點不同的節點作為頭節點,遞歸調用原方法,並直接返回
return deleteDuplication(next);
}else {
// 若頭節點與它的下一個節點值不同,則將頭節點的下一個節點作為頭節點,遞歸調用方法
// 並將返回值賦給頭節點的next屬性
pHead.next = deleteDuplication(next);
return pHead;
}
}
// 以下是節點ListNode
class ListNode {
int val;
ListNode next = null;
ListNode(int val) {
this.val = val;
}
}
上面的代碼我加了點注釋,看得難受可以復制到編輯器中,刪掉注釋再看。
上面這段代碼,給我的感覺就是把遞歸用的出神入化(可能是我太菜了)。除去注釋,短短幾行代碼,就將上面的思路完全實現,下面我來解讀一下:
上面的代碼首先做了特判,若傳入的頭節點是空,或者沒有后續節點,那就不用去重,直接返回。這之后,先將頭節點的下一個節點保存。
我們先判斷當前是否滿足前面說的第一種情況:頭節點發生了重復,若發生了這種情況,就一直向后找,直到找到第一個不與頭節點重復的節點,然后我們舍棄前面的節點,把這個節點當作頭節點,遞歸調用方法,並直接將返回值返回,這相當於是把后面剩下的部分當作一條新的鏈表,而前面重復的就直接舍棄了;
若當前鏈表是我們之前說的第二種情況:頭節點不重復,則我們將頭節點的下一個節點作為參數,遞歸調用原方法,將除去頭節點后的子鏈表看作是一個新鏈表,而方法返回值就是這個子鏈表去重后的鏈表,我們將其與原來頭節點關聯,就完整地去重了。
上面代碼最精妙的地方就是遞歸,將原鏈表中除去頭節點的剩余部分,當作一個新鏈表進行處理,短短幾行代碼,就實現了去重。