1. 程式人生 > 其它 >演算法刷題之七連結串列

演算法刷題之七連結串列

連結串列

題目:

  1. 連結串列刪除某一個節點
  2. 刪除倒數第N個節點
  3. 連結串列逆序
  4. 迴文連結串列
  5. 判斷連結串列是否有環路
  6. 找出環路連結串列的入口
  7. 連結串列排序
  8. 相交連結串列
  9. 兩個連表生成相加連結串列

連結串列的資料格式就是如下:

需要注意的是:連結串列尾部的next為None。所以判斷連結串列結束時,這是遍歷一個連結串列最常用的結束方式。使用的程式碼:

while p != None:
    p = p.next

其實是當p已經指向了next區域,這時next為空,所以能夠退出

當使用p.next != None時,這時p指向的最有一個節點,而不是節點的next。

while p.next != None:
    p = p.next

連結串列刪除某一個節點:

刪除節點兩種方式:

  1. 當前節點指向下下個節點

  2. 將下一個節點賦值給當前節點,然後刪除下一個節點

# 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個節點

有兩種方式:

  1. 算出倒數N的正數num,然後移動到該節點前一個,刪除節點
  2. 設定雙指標,指標的間距是倒數N。當快指標到鏈尾時,慢指標當好到待刪除的前一個。
  3. 刪除節點需要考慮節點為頭結點的情況。這時指標指向頭結點,不易刪除。解決辦法是給頭結點增加一個前行節點。將指標指向該前行節點。
# 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/

迴文最常用的判斷方式是:

  1. 逆序,然後比較逆序之後和逆序之前是否相同。如果相同就是迴文,不同就不是迴文
  2. 將前一半儲存,逆序或者入棧,和後一半比較。如果都相同則是迴文。

連結串列迴文有多種方式:

  1. 將連結串列中所有資料儲存到列表中,使用列表的逆序來判斷
  2. 利用快慢指標,前一半連結串列的值入棧,然後出棧和後一半比較判斷
  3. 利用快慢指標,逆序前一半連結串列,和後一半比較
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)

連結串列解題總結

連結串列是我喜歡的資料結構,因為連結串列沒有太多複雜操作。通常是遍歷連結串列一直到尾節點,然後一邊遍歷一邊配合指標操作。在連結串列中有幾個小技巧可以好好總結一下:

  1. 使用快慢指標可以找到連結串列的中間位置。
    一個指標是快指標,每次走兩步,一個指標是慢指標,每次走一步。當快指標到末尾時,慢指標剛好在連結串列的中間位置。使用場景:迴文連結串列,歸併排序
while first and first.next:
    first = first.next.next
    second = second.next
  1. 帶頭結點的指標更好操作。在函式中,建立一個帶頭結點指標更方便操作。
    node = ListNode(None)
    使用場景:刪除節點,翻轉節點

  2. 連結串列取奇偶很簡單,next的next就能保證奇偶