Leetcode【61、82、83、142、143、1171】

  • 2019 年 10 月 29 日
  • 筆記

問題描述:【Linked List】61. Rotate List 解題思路:

這道題是給一個鏈表,旋轉鏈表,將鏈表每個結點向右移動 k 個位置。

1、先計算鏈表長度 size,k = k % size,如果 k % size == 0,則不用移動,直接返回 head; 2、否則,需要將前 size – k 個結點移動到後面。因此只需要循環 size – k 次,找到新鏈表頭部,然後進行指針的交換。最後返回新鏈表頭即可。

注意:這道題雖然是旋轉鏈表,但是實際上並沒有真正的進行結點的移動,只是進行了指針的交換。

時間複雜度為 O(n),空間複雜度為 O(1)。

Python3 實現:
# Definition for singly-linked list.  # class ListNode:  #     def __init__(self, x):  #         self.val = x  #         self.next = None    class Solution:      def rotateRight(self, head: ListNode, k: int) -> ListNode:          if not head:              return head          cur, size = head, 1          while cur.next:  # cur 結束後記錄鏈表尾              size += 1              cur = cur.next          k = k % size          if k == 0:  # 不用旋轉              return head          i, pre, newhead = 0, None, head  # pre:newhead的前一個, newhead:新頭部          while i < size - k:              i += 1              pre = newhead              newhead = newhead.next          cur.next = head          pre.next = None          return newhead

問題描述:【Linked List】82. Remove Duplicates from Sorted List II 解題思路:

這道題是給一個鏈表,去除鏈表中重複的元素,只留下原鏈表出現過一次的數字。如 1->2->2->3 變成 1->3。

這道題和下面的 Leetcode 82 思路相同,只不過這道題不需要留下重複的元素。因此除了 Leetcode 82 中的 cur 和 last 外,還需要一個 pre 指向 cur 的前一個位置,便於把所有相同的 cur 全部刪除。 同時,要使用一個標記變數 flag 來記錄連續一段有沒有重複的元素(flag = True),如果沒有重複,只是修改 pre 和 cur 向後各移動一位;否則還要進行指針的交換。注意:比如 [1,2,2,2,2] 這種,循環結束了,但是最後 flag 為 True,因此還需要再進行一次判斷,如果 flag 為 True,要進行一次指針的交換操作。

時間複雜度為 O(n),空間複雜度為 O(1)。

Python3 實現:
# Definition for singly-linked list.  # class ListNode:  #     def __init__(self, x):  #         self.val = x  #         self.next = None    class Solution:      def deleteDuplicates(self, head: ListNode) -> ListNode:          node = ListNode(-1)          node.next = head          head = node          if not head.next:              return head.next          # cur: 指向第一個重複的元素,last: 工作指針,pre: 是 cur 的前一個位置          pre, cur, last = head, head.next, head.next.next          flag = False          while last:              if cur.val == last.val:                  flag = True                  cur.next = last.next              elif flag:  # 有重複                  pre.next = last                  cur = last                  flag = False    # 不要忘了修改 flag 為 False,標記下一段是否可能有重複              elif not flag:  # 沒有重複                  pre = cur                  cur = last              last = last.next          if flag:  # [1,1]或者[1,2,2]               pre.next = last          return head.next

問題描述:【Linked List】83. Remove Duplicates from Sorted List 解題思路:

這道題是給一個鏈表,去除鏈表中重複的元素。如 1->2->2->3 變成 1->2->3。

只需要兩個指針 cur 和 last,cur 指向相同元素的第一個結點,last 為工作指針,每次向後移動。剩下的就是指針的交換和移動。時間複雜度為 O(n),空間複雜度為 O(1)。

Python3 實現:
# Definition for singly-linked list.  # class ListNode:  #     def __init__(self, x):  #         self.val = x  #         self.next = None    class Solution:      def deleteDuplicates(self, head: ListNode) -> ListNode:          if not head:              return head          cur, last = head, head.next          while last:              if cur.val == last.val:                  cur.next = last.next              else:                  cur = cur.next              last = last.next          return head

問題描述:【Two Pointers】142. Linked List Cycle II 解題思路:

這道題是給一個鏈表,判斷是否有環。如果有環,找到環的起始位置。

這道題和 Leetcode 【Two Pointers】141. Linked List Cycle 思路一致,都是先使用快慢指針(一個走一步,一個走兩步)判斷是否有環。

對於這道題,如果有環,還要尋找環的起始位置。思路是:定義一個指針 begin 指向鏈表頭,然後和快慢指針的相遇點 slow(或者 fast),每一次都各走一步,直到二者相遇。證明就不寫了,可以參考:LeetCode-142. Linked List Cycle II(詳細證明)與龜兔賽跑演算法 的證明過程。

時間複雜度為 O(n),空間複雜度為 O(1)。

Python3 實現:
# Definition for singly-linked list.  # class ListNode:  #     def __init__(self, x):  #         self.val = x  #         self.next = None    class Solution:      def detectCycle(self, head: ListNode) -> ListNode:          slow = fast = head          flag = True  # 標記是否有環          while fast and fast.next:              slow = slow.next              fast = fast.next.next              if slow == fast:  # 有環                  flag = False                  break          if flag:  # 無環              return None          begin = head          while begin != slow:              begin = begin.next              slow = slow.next          return begin # return slow

問題描述:【Linked List】143. Reorder List 解題思路:

這道題是給一個鏈表,按照 L0→Ln→L1→Ln-1→L2→Ln-2→… 重新排序。

首先第一種想法:遍歷一次鏈表,將各個結點保存到 list 中,按題目順序重新構造鏈表即可。更進一步,我們只需要保存後一半鏈表元素到 list 中,然後將 list 中的元素插入到前半段鏈表中。但是,這樣的操作時間複雜度和空間複雜度均為 O(n)。

有沒有時間複雜度為 O(n)、空間複雜度為 O(1) 的做法?因為後半段需要反過來插入,因此我們可以對後半段鏈表進行反轉,然後再按順序插入到前半段鏈表就行。鏈表反轉可以參考 Leetcode 【Linked List】206. Reverse Linked List

實際上,當我們遇到需要從尾部操作的鏈表問題(如這道題和 Leetcode 【Linked List】445. Add Two Numbers II),都可以先將鏈表反轉,然後再操作,這樣就不用使用額外空間了。

Python3 實現:
# Definition for singly-linked list.  # class ListNode:  #     def __init__(self, x):  #         self.val = x  #         self.next = None    class Solution:      def reorderList(self, head: ListNode) -> None:          """          Do not return anything, modify head in-place instead.          """          if not head:              return head          l1 = slow = head          fast = head.next          while fast and fast.next:              slow = slow.next              fast = fast.next.next          l2 = self.reverse(slow.next)  # 後半段反轉          slow.next = None  # 斷開前半段          cur1, cur2 = l1, l2          while cur1 and cur2:  # 將後半段依次插入前半段              l2 = l2.next  # 插入              cur2.next = cur1.next              cur1.next = cur2              cur1 = cur2.next  # 移動              cur2 = l2          return l1        def reverse(self, head):          if not head or not head.next:              return head          pre, cur = head, head.next          while cur:              pre.next = cur.next  # 交換              cur.next = head              head = cur              if pre != None:                  cur = pre.next  # 移動          return head

問題描述:【Hash Table】1171. Remove Zero Sum Consecutive Nodes from Linked List 解題思路:

這道題是給一個鏈表,反覆刪去鏈表中總和為 0 的連續結點組成的序列,直到不存在這樣的序列為止。

很明顯這是一道前綴和問題。之前做過前綴和的一個專題:Leetcode【523、525、560、974】,因此需要用到 Hash Table 存儲未出現的前綴和與對應位置的鏈表結點地址。如果再出現相同前綴和,則就刪除兩前綴和地址之間的元素即可(修改指針指向)。

注意: 1、要把 {0: None} 加入到 Hash Table 中,作為前綴和的出口(如 [1,2,-3] 這種情況)。 2、對於鏈表 [1,3,2,-3,-2,5,5,-5,1],從左到右遍歷,剛開始得到的前綴和分別為 {0: add(None), 1: add(1), 4: add(3), 6: add(2), 3: add(-3)},當計算到 -2 位置時,前綴和為 3 + (-1) = 1,前綴和 1 之前在 Hash Table 中出現過,因此需要將 add(1) 的下一個地址指向 -2 的下一個地址 add(5)。但是要注意,這時候還要標記 add(1) 後面的前綴和不可用(因為已經被刪除了)。因此,這個時候可以再使用一個集合 discard,用來記錄刪除的那些地址(從 add(1) 的下一個位置開始循環,一直到 add(-2))。因此,不僅前綴和要在之前出現過,而且前綴和地址不是 discard 中刪除的地址,才可以進行刪除。如果不這樣做,當碰到 add(5) 時,前綴和為 6,又要刪除,從而造成錯誤的結果。

時間複雜度為 O(n^2),空間複雜度為 O(n)。

Python3 實現:
# Definition for singly-linked list.  # class ListNode:  #     def __init__(self, x):  #         self.val = x  #         self.next = None    class Solution:      def removeZeroSumSublists(self, head: ListNode) -> ListNode:          node = ListNode(-1)          node.next = head          head = node          dic = {0: head}  # 前綴和:地址          discard = set()  # 刪除的地址          presum = 0          cur = head.next          while cur:              presum += cur.val              if presum in dic and dic[presum] not in discard:                  p = dic[presum].next  # 刪除的地址保存在集合中                  while p != cur:                      discard.add(p)                      p = p.next                  dic[presum].next = cur.next  # 指針移動              else:                  dic[presum] = cur              cur = cur.next          return head.next