演算法刷題之七連結串列
連結串列
題目:
- 連結串列刪除某一個節點
- 刪除倒數第N個節點
- 連結串列逆序
- 迴文連結串列
- 判斷連結串列是否有環路
- 找出環路連結串列的入口
- 連結串列排序
- 相交連結串列
- 兩個連表生成相加連結串列
連結串列的資料格式就是如下:
需要注意的是:連結串列尾部的next為None
。所以判斷連結串列結束時,這是遍歷一個連結串列最常用的結束方式
。使用的程式碼:
while p != None:
p = p.next
其實是當p已經指向了next區域,這時next為空,所以能夠退出
當使用p.next != None
時,這時p指向的最有一個節點,而不是節點的next。
while p.next != None: p = p.next
連結串列刪除某一個節點:
刪除節點兩種方式:
-
當前節點指向下下個節點
-
將下一個節點賦值給當前節點,然後刪除下一個節點
# Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: def deleteNode(self, node): """ :type node: ListNode :rtype: void Do not return anything, modify node in-place instead. """ node.val,node.next.val = node.next.val,node.val node.next = node.next.next
刪除倒數第N個節點
有兩種方式:
- 算出倒數N的正數num,然後移動到該節點前一個,刪除節點
- 設定雙指標,指標的間距是倒數N。當快指標到鏈尾時,慢指標當好到待刪除的前一個。
- 刪除節點需要考慮節點為頭結點的情況。這時指標指向頭結點,不易刪除。解決辦法是給頭結點增加一個前行節點。將指標指向該前行節點。
# Definition for singly-linked list. # class ListNode: # def __init__(self, x): # self.val = x # self.next = None class Solution: def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: # 設定頭結點 re = ListNode(-1) re.next = head slow = re fast = slow while n + 1> 0: fast = fast.next n -= 1 while fast != None: fast = fast.next slow = slow.next slow.next = slow.next.next return re.next
連結串列逆序
使用雙指標可以將連結串列逆序。
class Solution:
def reverseList(self, head: ListNode) -> ListNode:
pre = None
cur = head
while cur:
temp = cur.next # 先把原來cur.next位置存起來
cur.next = pre
pre = cur
cur = temp
return pre
需要注意結束條件。當cur == None
時,pre剛好在鏈尾的位置。返回pre就是返回了新的連結串列
迴文連結串列
請判斷一個連結串列是否為迴文連結串列。
示例 1:
輸入: 1->2
輸出: false
示例 2:
輸入: 1->2->2->1
輸出: true
進階:
你能否用 O(n) 時間複雜度和 O(1) 空間複雜度解決此題?
作者:力扣 (LeetCode)
連結:https://leetcode-cn.com/leetbook/read/top-interview-questions-easy/xnv1oc/
迴文最常用的判斷方式是:
- 逆序,然後比較逆序之後和逆序之前是否相同。如果相同就是迴文,不同就不是迴文
- 將前一半儲存,逆序或者入棧,和後一半比較。如果都相同則是迴文。
連結串列迴文有多種方式:
- 將連結串列中所有資料儲存到列表中,使用列表的逆序來判斷
- 利用快慢指標,前一半連結串列的值入棧,然後出棧和後一半比較判斷
- 利用快慢指標,逆序前一半連結串列,和後一半比較
class Solution:
def isPalindrome(self, head: ListNode) -> bool:
if not head or not head.next:
return True
slow = head
fast = head
mystack = []
while fast and fast.next:
mystack.append(slow.val)
fast = fast.next.next
slow = slow.next
# 奇數狀態,需要跨過中間的元素
if fast:
slow = slow.next
while slow and slow.val == mystack.pop():
slow = slow.next
判斷連結串列是否有環路
判斷連結串列是否有環路有兩個解決方法,
第一:快慢指標,如果存在環路,快指標一定會追上慢指標
第二:字典。將遍歷過的節點存入到字典中,每次遍歷時在字典中查詢,如果存在則表明有環路。
class Node():
def __init__(self, data):
self.data = data
self.next = None
node1 = Node(100)
node2 = Node(200)
node3 = Node(300)
node4 = Node(300)
node5 = Node(200)
node6 = Node(100)
node1.next = node2
node2.next = node3
node3.next = node4
node4.next = node5
node5.next = node6
node6.next = node1
def cycle_link(head):
fast = head
slow = head
while fast and fast.next:
fast = fast.next.next
slow = slow.next
if fast == slow:
return True
return False
res = cycle_link(node1)
print(res)
class Solution:
def hasCycle(self, head: ListNode) -> bool:
if not head:
return False
Hash = {}
temp = head
while temp != None:
if Hash.get(temp):
return True
else:
Hash[temp] = True
temp = temp.next
return False
找出環路連結串列的入口
通過計算可以知道,快指標走的距離是慢指標距離的兩倍,同時快指標比慢指標多走了一個圓的距離。所以在快慢指標相遇的地方,兩個指標以相同的資料在走下去,相遇的地方就是環的入口
class Solution:
def detectCycle(self, head: ListNode) -> ListNode:
first = head
second = head
while first and first.next:
first = first.next.next
second = second.next
if first == second:
first = head
while True:
if first == second:
return first
first = first.next
second = second.next
return None
連結串列排序
使用連結串列這樣資料結構實現排序,歸併排序的實現如下:
先分開,後合併
def sortList(self, head: ListNode) -> ListNode:
if not head or not head.next:
return head
slow = head
fast = head
# 用快慢指標分成兩部分
while fast.next and fast.next.next:
slow = slow.next
fast = fast.next.next
# 找到左右部分, 把左部分最後置空
mid = slow.next
slow.next = None
# 遞迴下去
left = self.sortList(head)
right = self.sortList(mid)
# 合併
return self.merge(left, right)
def merge(self, left, right):
dummy = ListNode(0)
p = dummy
l = left
r = right
while l and r:
if l.val < r.val:
p.next = l
l = l.next
p = p.next
else:
p.next = r
r = r.next
p = p.next
if l:
p.next = l
if r:
p.next = r
return dummy.next
相交連結串列
編寫一個程式,找到兩個單鏈表相交的起始節點。
在節點 c1 開始相交。
原理:兩個連結串列是否有相交的地方,可以通過如下方法測試出來:
走完A之後,從B起始點開始走,同樣,走完B之後,從A開始走,這樣如果有相交的位置,那麼A和B到相交的位置就剛好一致。
class Solution:
def getIntersectionNode(self, headA: ListNode, headB: ListNode) -> ListNode:
if not(headA and headB):
return None
nodea = headA
nodeb = headB
while nodea != nodeb:
if nodea is None:
nodea = headB
else:
nodea = nodea.next
if nodeb is None:
nodeb = headA
else:
nodeb = nodeb.next
return nodea
兩兩交換連結串列中的節點
給定一個連結串列,兩兩交換其中相鄰的節點,並返回交換後的連結串列。
你不能只是單純的改變節點內部的值,而是需要實際的進行節點交換。
輸入:head = [1,2,3,4]
輸出:[2,1,4,3]
# Definition for singly-linked list.
# class ListNode:
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def swapPairs(self, head: ListNode) -> ListNode:
pre_head = ListNode(-1)
pre_head.next = head
pre_h = pre_head
p = head
while p and p.next:
next_p = p.next
p.next = next_p.next
pre_h.next = next_p
next_p.next = p
pre_h = p
p = p.next
return pre_head.next
兩個連表生成相加連結串列
假設連結串列中每一個節點的值都在 0 - 9 之間,那麼連結串列整體就可以代表一個整數。
給定兩個這種連結串列,請生成代表兩個整數相加值的結果連結串列。
例如:連結串列 1 為 9->3->7,連結串列 2 為 6->3,最後生成新的結果連結串列為 1->0->0->0。
class Solution:
def addInList(self , head1 , head2 ):
def reverse_fun(head):
pre = None
p = head
while p:
temp = p.next
p.next = pre
pre = p
p = temp
return pre
re_head1 = reverse_fun(head1)
re_head2 = reverse_fun(head2)
d = ListNode(0)
p = d
c = 0
while re_head1 or re_head2 or c:
if re_head1:
c += re_head1.val
re_head1 = re_head1.next
if re_head2:
c += re_head2.val
re_head2 = re_head2.next
p.next = ListNode(c % 10)
p = p.next
c = c // 10
return reverse_fun(d.next)
連結串列解題總結
連結串列是我喜歡的資料結構,因為連結串列沒有太多複雜操作。通常是遍歷連結串列一直到尾節點,然後一邊遍歷一邊配合指標操作。在連結串列中有幾個小技巧可以好好總結一下:
- 使用快慢指標可以找到連結串列的中間位置。
一個指標是快指標,每次走兩步,一個指標是慢指標,每次走一步。當快指標到末尾時,慢指標剛好在連結串列的中間位置。使用場景:迴文連結串列,歸併排序
while first and first.next:
first = first.next.next
second = second.next
-
帶頭結點的指標更好操作。在函式中,建立一個帶頭結點指標更方便操作。
node = ListNode(None)
使用場景:刪除節點,翻轉節點 -
連結串列取奇偶很簡單,next的next就能保證奇偶