算法工程師面試必考項——鏈表
AI作者:田旭
AI編輯:田旭
1 知識點
1.1 什么是鏈表
提到鏈表,我們大家都不陌生,在平時的編碼中我們也或多或少地使用過這個數據結構。算法(第4版) (豆瓣)一書中對鏈表的定義如下:
鏈表是一種遞歸的數據結構,它或者為空(null),或者是指向一個結點(node)的引用,該節(jié)點還有一個元素和一個指向另一條鏈表的引用。
鏈表(Linked list)是一種常見的基礎數據結構,是一種線性表,但是并不會按線性的順序存儲數據,而是在每一個節(jié)點里存到下一個節(jié)點的指針(Pointer)。由于不必須按順序存儲,鏈表在插入的時候可以達到O(1)的復雜度,比另一種線性表順序表快得多,但是查找一個節(jié)點或者訪問特定編號的節(jié)點則需要O(n)的時間,而順序表相應的時間復雜度分別是O(logn)和O(1)。
使用鏈表結構可以克服數組鏈表需要預先知道數據大小的缺點,鏈表結構可以充分利用計算機內存空間,實現靈活的內存動態(tài)管理。但是鏈表失去了數組隨機讀取的優(yōu)點,同時鏈表由于增加了結點的指針域,空間開銷比較大。
1.2 鏈表結構
1.2.1 單向鏈表
鏈表中最簡單的一種是單向鏈表,它包含兩個域,一個信息域和一個指針域。這個鏈接指向列表中的下一個節(jié)點,而最后一個節(jié)點則指向一個空值。

一個單向鏈表包含兩個值: 當前節(jié)點的值和一個指向下一個節(jié)點的鏈接
一個單向鏈表的節(jié)點被分成兩個部分。第一個部分保存或者顯示關于節(jié)點的信息,第二個部分存儲下一個節(jié)點的地址。單向鏈表只可向一個方向遍歷。
1.2.2 雙向鏈表
一種更復雜的鏈表是“雙向鏈表”或“雙面鏈表”。每個節(jié)點有兩個連接:一個指向前一個節(jié)點,(當此“連接”為第一個“連接”時,指向空值或者空列表);而另一個指向下一個節(jié)點,(當此“連接”為最后一個“連接”時,指向空值或者空列表)。

一個雙向鏈表有三個整數值: 數值, 向后的節(jié)點鏈接, 向前的節(jié)點鏈接
雙向鏈表也叫雙鏈表。雙向鏈表中不僅有指向后一個節(jié)點的指針,還有指向前一個節(jié)點的指針。這樣可以從任何一個節(jié)點訪問前一個節(jié)點,當然也可以訪問后一個節(jié)點,以至整個鏈表。一般是在需要大批量的另外儲存數據在鏈表中的位置的時候用。雙向鏈表也可以配合下面的其他鏈表的擴展使用。
1.2.3 循環(huán)鏈表
在一個 循環(huán)鏈表中, 首節(jié)點和末節(jié)點被連接在一起。這種方式在單向和雙向鏈表中皆可實現。要轉換一個循環(huán)鏈表,你開始于任意一個節(jié)點然后沿著列表的任一方向直到返回開始的節(jié)點。再來看另一種方法,循環(huán)鏈表可以被視為“無頭無尾”。這種列表很利于節(jié)約數據存儲緩存, 假定你在一個列表中有一個對象并且希望所有其他對象迭代在一個非特殊的排列下。
指向整個列表的指針可以被稱作訪問指針。

用單向鏈表構建的循環(huán)鏈表
1.3鏈表相關的核心點
null/nil 異常處理 dummy node 啞巴節(jié)點 快慢指針 插入一個節(jié)點到排序鏈表 從一個鏈表中移除一個節(jié)點 翻轉鏈表 合并兩個鏈表 找到鏈表的中間節(jié)點
2 常見題型
83. 刪除排序鏈表中的重復元素
給定一個排序鏈表,刪除所有重復的元素,使得每個元素只出現一次。
思路:直接法,遇到重復就跳過
class Solution:def deleteDuplicates(self, head: ListNode) -> ListNode:cur = headwhile cur and cur.next:if cur.val == cur.next.val:cur.next = cur.next.nextelse:cur = cur.nextreturn head
82. 刪除排序鏈表中的重復元素 II
給定一個排序鏈表,刪除所有含有重復數字的節(jié)點,只保留原始鏈表中 沒有重復出現 的數字。
思路:定義啞節(jié)點指向鏈表頭,定義雙指針,一個指向當前節(jié)點,一個指向前一個節(jié)點。當有重復的元素時,cur指向下一位,直到下一位與當前節(jié)點不相等,用flag標記表示有重復數字,當循環(huán)完畢,pre的下一位指向cur的下一位,flag標記歸位
當沒有重復數字,pre指向cur
class Solution:def deleteDuplicates(self, head: ListNode) -> ListNode:if not head or not head.next: # 空鏈表或單即節(jié)點鏈表return headdummy = ListNode(0) # 定義啞節(jié)點dummy.next = headpre = dummy # 啞節(jié)點指針cur = head # 鏈表指針flag = False # 標記是否重復while cur:while cur.next and pre.next.val == cur.next.val:cur = cur.next # 將所有重復節(jié)點標記為Trueflag = Trueif flag is True: # 重復節(jié)點就跳過pre.next = cur.nextflag = Falseelse:pre = cur # 不重復就下一個節(jié)點cur = cur.nextreturn dummy.next
206. 反轉鏈表
反轉一個單鏈表。
思路1:迭代,每次存儲前一個節(jié)點,并讓當前節(jié)點的next指針指向前一個節(jié)點
思路2:遞歸,假設我子節(jié)點下的所有節(jié)點都已經反轉好了,現在就剩我和我的子節(jié)點沒有完成最后的反轉了,所以反轉一下我和我的子節(jié)點。
# 迭代class Solution:def reverseList(self, head: ListNode) -> ListNode:pre = Nonecur = headwhile cur:temp = cur.next # 存儲節(jié)點的下一位cur.next = pre # 指向前驅節(jié)點pre = curcur = temp # cur指向原來的下一位return pre
# 遞歸class Solution:def reverseList(self, head: ListNode) -> ListNode:if not head or not head.next:return headp = self.reverseList(head.next)head.next.next = headhead.next = Nonereturn p
92. 反轉鏈表 II
反轉從位置 m 到 n 的鏈表。請使用一趟掃描完成反轉。
思路:用雙指針法,現將慢指針移動到要反轉的部分的前一個節(jié)點,用另一個指針指向慢指針后面的節(jié)點,然后兩兩交換節(jié)點
class Solution:def reverseBetween(self, head: ListNode, m: int, n: int) -> ListNode:dummy = ListNode(None)dummy.next = headpre = dummyfor i in range(1, m):pre = pre.next # 前驅指針移動到位置mcur = pre.nextfor i in range(m, n): # 兩兩交換節(jié)點temp = cur.nextcur.next = temp.next # cur一直不變,只修改指針到下一個位置temp.next = pre.next # temp.next指向pre的next,也就是最新的第m個位置pre.next = temp # 更新temp為最新的第m個位置return pre
21. 合并兩個有序鏈表
將兩個升序鏈表合并為一個新的 升序 鏈表并返回。新鏈表是通過拼接給定的兩個鏈表的所有節(jié)點組成的。
思路:建立新鏈表,依次按升序鏈接兩個鏈表的元素
class Solution:def mergeTwoLists(self, l1: ListNode, l2: ListNode) -> ListNode:dummy = ListNode(None)pre = dummywhile l1 and l2:if l1.val < l2.val:pre.next = l1l1 = l1.nextelse:pre.next = l2l2 = l2.nextpre = pre.nextpre.next = l1 if l1 else l2return dummy.next
86. 分隔鏈表
給定一個鏈表和一個特定值 x,對鏈表進行分隔,使得所有小于 x 的節(jié)點都在大于或等于 x 的節(jié)點之前。
思路:創(chuàng)建雙鏈表,一個存儲小于x的值,一個存儲大于x的值
class Solution:def partition(self, head: ListNode, x: int) -> ListNode:before = befornHead = ListNode(None)after = afterHead = ListNode(None)while head:if head.val < x:before.next = headbefore = before.nextelse:after.next = headafter = after.nexthead = head.nextafter.next = Nonebefore.next = afterHead.nextreturn befornHead.next
148. 排序鏈表
在 O(n log n) 時間復雜度和常數級空間復雜度下,對鏈表進行排序。
思路:歸并排序,找中點和合并操作
# 歸并排序(遞歸),空間復雜度O(n)class Solution:def sortList(self, head: ListNode) -> ListNode:if not head or not head.next:return headslow, fast = head, head.nextwhile fast and fast.next:slow, fast = slow.next, fast.next.nextmid = slow.nextslow.next = Noneleft = self.sortList(head)right = self.sortList(mid)h = res = ListNode(0)while left and right:if left.val < right.val:h.next, left = left, left.nextelse:h.next, right = right, right.nexth = h.nexth.next = left if left else rightreturn res.next
# 快速排序class Solution:def sortList(self, head: ListNode) -> ListNode:if head is None:return head# 分成三個鏈表,分別是比軸心數小,相等,大的數組成的鏈表big = Nonesmall = Noneequal = Nonecur = headwhile cur:temp = curcur = cur.nextif temp.val < head.val:temp.next = smallsmall = tempelif temp.val > head.val:temp.next = bigbig = tempelse:temp.next = equalequal = temp# 拆完各自排序即可,equal 無需排序big = self.sortList(big)small = self.sortList(small)ret = ListNode(None)cur = ret# 將三個鏈表組合成一起# 可以同時返回鏈表的頭指針和尾指針加速鏈表的合并。for p in [small, equal, big]:while p is not None:cur.next = pp = p.nextcur = cur.nextcur.next = Nonereturn ret.next
143. 重排鏈表
給定一個單鏈表 L:L0→L1→…→L**n-1→Ln , 將其重新排列后變?yōu)椋?em>L0→Ln→L1→Ln-1→L2→Ln-2→…
思路:找到中點斷開,翻轉后面部分,然后合并前后兩個鏈表
class Solution:def reorderList(self, head: ListNode) -> None:"""Do not return anything, modify head in-place instead."""if not head or not head.next:return headslow, fast = head, head# 找到鏈表中點while fast.next and fast.next.next:slow = slow.nextfast = fast.next.next# 反轉后半部分print(slow.next)right = Nonecur = slow.nextwhile cur:temp = cur.nextcur.next = rightright = curcur = temp# 將反轉的后半部分插入到前半部分left = headwhile left and right:slow.next = right.nextright.next = left.nextleft.next = rightleft = right.nextright = slow.next
141. 環(huán)形鏈表
給定一個鏈表,判斷鏈表中是否有環(huán)。
思路1:哈希表,統(tǒng)計每個元素出現的次數
思路2:快慢指針,快慢指針相同則有環(huán),證明:如果有環(huán)每走一步快慢指針距離會減 1
# 哈希表class Solution:def hasCycle(self, head: ListNode) -> bool:dic = {}while head:if head in dic:return Trueelse:dic[head] = 1head = head.nextreturn False
# 快慢指針class Solution:def hasCycle(self, head: ListNode) -> bool:if head is None or head.next is None:return Falseslow = headfast = slow.nextwhile fast and fast.next:fast = fast.next.nextslow = slow.nextif fast == slow:return Truereturn False
142. 環(huán)形鏈表 II
給定一個鏈表,返回鏈表開始入環(huán)的第一個節(jié)點。如果鏈表無環(huán),則返回
null。
思路:快慢指針,快慢相遇之后,慢指針回到頭,快慢指針步調一致一起移動,相遇點即為入環(huán)點
class Solution:def detectCycle(self, head: ListNode) -> ListNode:fast, slow = head, headwhile True:if not (fast and fast.next):returnslow = slow.nextfast = fast.next.nextif slow == fast:breakfast = headwhile fast != slow:fast = fast.nextslow = slow.nextreturn fast
138. 復制帶隨機指針的鏈表
給定一個鏈表,每個節(jié)點包含一個額外增加的隨機指針,該指針可以指向鏈表中的任何節(jié)點或空節(jié)點。
要求返回這個鏈表的 深拷貝。
思路:1、hash 表存儲指針,2、復制節(jié)點跟在原節(jié)點后面
class Solution:def copyRandomList(self, head: 'Node') -> 'Node':if not head:returndic = {}dic[None] = Nonecur = headwhile cur:if cur not in dic:dic[cur] = Node(cur.val) # 將當前未拷貝的節(jié)點放入字典中if cur.random not in dic:dic[cur.random] = Node(cur.random.val) # 將當前未拷貝的random指針放入字典中dic[cur].random = dic[cur.random]if cur.next not in dic:dic[cur.next] = Node(cur.next.val) # 將當前未拷貝的next指針放入字典中dic[cur].next = dic[cur.next]cur = cur.nextreturn dic[head]
234. 回文鏈表
請判斷一個鏈表是否為回文鏈表。
思路1:將值復制到數組中,時間復雜度O(n),空間復雜度O(n)
思路2:快慢指針,先找到鏈表中點,然后反轉后半部分,再與前半部分比較,時間復雜度O(n),空間復雜度O(1)
# 將值復制到數組中class Solution:def isPalindrome(self, head: ListNode) -> bool:visit = []cur = headwhile cur:visit.append(cur.val)cur = cur.nextreturn visit == visit[::-1]
# 找中點 -> 反轉 -> 比較class Solution:def isPalindrome(self, head: ListNode) -> bool:pre = Noneslow, fast = head, headwhile fast and fast.next:slow = slow.next # 使慢指針指向中間節(jié)點fast = fast.next.nextwhile slow: # 反轉后半部分,pre指向反轉部分temp = slow.nextslow.next = prepre = slowslow = tempwhile head and pre: # 比較兩部分是否相同if head.val != pre.val:return Falsehead = head.nextpre = pre.nextreturn True
如果覺得不錯,請關注一下公眾號,也請為小編點個在看!
推薦閱讀
機器學習算法工程師
? ??? ? ? ? ? ? ? ? ? ? ? ??????????????????一個用心的公眾號

