LeetCode 82,考察你的基本功,在有序連結串列中刪除重複元素II
今天是LeetCode專題的第51篇文章,我們來看LeetCode第82題,刪除有序連結串列中的重複元素II(Remove Duplicates from Sorted List II)。
這題官方給出的難度是Medium,點贊1636,反對107,通過率在36.3%左右。根據我們之前的分析,這題的難度適中,並且質量很高,好評如潮。實際上也的確如此,這題演算法本身並不難,但是想要完整沒有bug地實現並不容易,我們一起來看看。
題意
給定一個有序的存在重複元素的連結串列,要求移除掉連結串列當中所有的重複元素。返回一個不包含重複元素的連結串列。
這裡要注意的一點,這題讓我們做的事情並不是去重,就是去除掉多餘的元素,而是要去除掉所有重複的元素。比如2在連結串列當中出現了兩次,屬於重複元素,我們要做的並不是去掉一個2,僅保留一個,而是要將所有的2都去除,因為2屬於重複元素。
我們來看樣例:
Input: 1->2->3->3->4->4->5
Output: 1->2->5
原連結串列當中的3和4都屬於重複元素,所以被去除了。
Input: 1->1->1->2->3
Output: 2->3
解法
前面說了這題的質量很高,這題是屬於典型的解法赤裸裸,但是很多人就是寫不出來的題,非常考察基本功。適合用在校招面試當中,如果我有幸去面試校招生, 我可能會選這道題。不存在演算法會不會的問題,寫不出來一定是基本功不夠紮實。
連結串列已經有序了,那麼相同的元素必然會排在一起,我們只需要將它們移除就可以了。但是說起來簡單,要在連結串列當中實現並不容易。難點主要有兩個,一個是連結串列增刪節點的操作很多人不熟悉,尤其是像是C++這樣的語言涉及指標,可能更不容易。另外一個難點就在題意當中,我們要做的不是去重,而是要所有重複的元素全部刪除。
看起來似乎和去重沒什麼差別, 如果你真這麼想,並且著手去實現,那麼幾乎可以肯定一定會遇到問題。
因為連結串列是單向的,假設你當前的指標是cur,當你發現cur這個指標的元素存在重複的時候,你需要連當前這個節點一起刪除。我們都知道,單向連結串列是不能走回頭路的,而刪除節點,必須要用到前一個節點的指標。再加上判斷元素重複需要用到的指標,會需要我們同時維護多個指標,增加程式碼的編碼難度。
針對這個問題,我們有兩種解決思路。第一種是我們不在原連結串列上處理,而是建立一個新的連結串列進行返回。所以我們要做的就不是刪除元素,而是插入元素,只有發現當前元素不存在重複的時候才會插入連結串列。最後返回的也是一個全新的連結串列。
這當然是可以的,也是沒有問題了,我第一次做這道題就是採取的這種措施。相比之下, 這種方法會容易一些,因為我們不需要判斷太多的指標和位置,我貼一下當時的程式碼:
class Solution {
public:
ListNode* deleteDuplicates(ListNode* head) {
// 如果元素少於2個則直接返回
if (head == nullptr || head->next == nullptr) return head;
int cur = head->val;
// 初始化
bool flag = false;
ListNode* ct = new ListNode(0);
ListNode* pnt = ct;
head = head->next;
// 遍歷元素
while (head) {
int hd = head->val;
// 判斷當前元素與之前的元素是否相等
if (hd != cur) {
// 之前的元素沒有出現重複
if (!flag) {
// 把之前的元素存入連結串列
pnt->next = new ListNode(cur);
// 連結串列移動
pnt = pnt->next;
}else flag = false;
// 更新之前的元素
cur = hd;
}else flag = true;
head = head->next;
}
// 單獨處理最後一個元素
if (!flag) pnt->next = new ListNode(cur);
return ct->next;
}
};
雖然題目當中沒有對解法做出限制,也沒有規定我們必須要在原連結串列上進行處理,但是這種建立新連結串列的方法終歸有繞開問題的嫌疑。所以我們還有第二種解法,就是直面問題,我們維護多個指標,判斷當前位置的下一個元素是否構成重複。如果重複,則刪除掉重複的部分。
正如我們之前所說的那樣,在單向連結串列當中很難刪除當前元素,所以我們判斷下一個元素是否會構成重複。如果重複的話,進行刪除要可行許多。這樣也有一個問題就是,有可能連結串列的第一個元素就是重複的,我們沒有辦法找到第一個元素的上一個元素。針對這個問題,我們採用的方法是人為給它創造一個元素放在首元素之前。這樣我們整個流程就可以串起來了,唯一的難點就是編碼了。
我們仔細一些,寫出程式碼還是可以的:
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def deleteDuplicates(self, head: ListNode) -> ListNode:
# 建立一個新的元素放在連結串列頭部,這樣原本第一個元素就是頭指標的下一個元素了
node = ListNode()
node.next = head
# pnt指向新建立的元素
pnt = node
while pnt.next is not None:
# cur指向當前位置的下一個位置
# 我們要做的即判斷cur這個指標的元素是否重複
cur = pnt.next
if cur.next is None:
break
# ptr指向cur的next
ptr = cur.next
# 如果ptr和cur相等,那麼出現重複,我們需要跳過所有相等的元素
if ptr is not None and ptr.val == cur.val:
while ptr is not None and ptr.val == cur.val:
ptr = ptr.next
pnt.next = ptr
# 否則說明不重複,移動pnt
else:
pnt = pnt.next
# 由於我們開始的時候人為添加了一個輔助元素
# 返回的時候要將它去除
return node.next
總結
這道題的演算法很簡單,我想大部分人都能想出解法來,但是要將解法實現並不太容易。這當中用到了很多小技巧,比如我們認為建立了一個新的頭結點,比如我們將刪除當前元素轉化成了刪除下一個元素等等。這些技巧雖然算不上什麼,但是靈活使用,可以大大降低我們編碼的複雜度,也正是因為這一點,這題的質量非常高,值得一做。
很多人非常討厭涉及連結串列的問題,覺得連結串列很難操作,容易寫錯,但實際上這是基本功的很重要的一部分。很多公司喜歡考察候選人的基本功,提升這方面的能力對於我們應聘或者是工作非常有幫助。
今天的文章到這裡就結束了,如果喜歡本文的話,請來一波素質三連,給我一點支援吧(關注、轉發、點贊)。
本文使用 mdnice 排版