鏈表(python)


簡介 - 單鏈表

單鏈表中的每個結點不僅包含值,還包含鏈接到下一個結點的引用字段。通過這種方式,單鏈表將所有結點按順序組織起來。、

下面是一個單鏈表的例子:

正如你所看到的,鏈表中的每個元素實際上是一個單獨的對象,而所有對象都通過每個元素中的引用字段鏈接在一起。

鏈表有兩種類型:單鏈表和雙鏈表。上面給出的例子是一個單鏈表,這里有一個雙鏈表的例子:

我們將在接下來的章節中介紹更多內容。完成這張卡片后,你將:

  • 了解單鏈表和雙鏈表的結構;
  • 在單鏈表或雙鏈表中實現遍歷、插入和刪除;
  • 分析在單鏈表或雙鏈表中的各種操作的復雜度;
  • 在鏈表中使用雙指針技巧(快指針慢指針技巧);
  • 解決一些經典問題,例如反轉鏈表;
  • 分析你設計的算法的復雜度;
  • 積累設計和調試的經驗。

單鏈表中的每個結點不僅包含值,還包含鏈接到下一個結點的引用字段。通過這種方式,單鏈表將所有結點按順序組織起來。、

下面是一個單鏈表的例子:

藍色箭頭顯示單個鏈接列表中的結點是如何組合在一起的。

在大多數情況下,我們將使用頭結點(第一個結點)來表示整個列表。

操作


與數組不同,我們無法在常量時間內訪問單鏈表中的隨機元素。 如果我們想要獲得第 i 個元素,我們必須從頭結點逐個遍歷。 我們按索引訪問元素平均要花費 O(N) 時間,其中 N 是鏈表的長度。

例如,在上面的示例中,頭結點是 23。訪問第 3 個結點的唯一方法是使用頭結點中的“next”字段到達第 2 個結點(結點 6); 然后使用結點 6 的“next”字段,我們能夠訪問第 3 個結點。

你可能想知道為什么鏈表很有用,盡管它在通過索引訪問數據時(與數組相比)具有如此糟糕的性能。 在接下來的兩篇文章中,我們將介紹插入和刪除操作,你將了解到鏈表的好處。

 

 

添加操作 - 單鏈表

如果我們想在給定的結點 prev 之后添加新值,我們應該:

  1. 使用給定值初始化新結點 cur;
  2. 將 cur 的“next”字段鏈接到 prev 的下一個結點 next
  3. 將 prev 中的“next”字段鏈接到 cur 。

與數組不同,我們不需要將所有元素移動到插入元素之后。因此,您可以在 O(1) 時間復雜度中將新結點插入到鏈表中,這非常高效。

 

示例


讓我們在第二個結點 6 之后插入一個新的值 9。

我們將首先初始化一個值為 9 的新結點。然后將結點 9 鏈接到結點 15。最后,將結點 6 鏈接到結點 9。

插入之后,我們的鏈表將如下所示:

 

在開頭添加結點


眾所周知,我們使用頭結點來代表整個列表。

因此,在列表開頭添加新節點時更新頭結點 head 至關重要。

  1. 初始化一個新結點 cur;
  2. 將新結點鏈接到我們的原始頭結點 head
  3. 將 cur 指定為 head

例如,讓我們在列表的開頭添加一個新結點 9。

  1. 我們初始化一個新結點 9 並將其鏈接到當前頭結點 23。
  2. 指定結點 9 為新的頭結點。 

如何在列表的末尾添加新的結點?我們還能使用類似的策略嗎?

 

  刪除操作 - 單鏈表

如果我們想從單鏈表中刪除現有結點 cur,可以分兩步完成:

  1. 找到 cur 的上一個結點 prev 及其下一個結點 next;

  2. 接下來鏈接 prev 到 cur 的下一個節點 next。

在我們的第一步中,我們需要找出 prev 和 next。使用 cur 的參考字段很容易找出 next,但是,我們必須從頭結點遍歷鏈表,以找出 prev,它的平均時間是 O(N),其中 N 是鏈表的長度。因此,刪除結點的時間復雜度將是 O(N)

空間復雜度為 O(1),因為我們只需要常量空間來存儲指針。

 

示例


讓我們嘗試把結點 6從上面的單鏈表中刪除。

1. 從頭遍歷鏈表,直到我們找到前一個結點 prev,即結點 23

2. 將 prev(結點 23)與 next(結點 15)鏈接

結點 6 現在不在我們的單鏈表中。

 

刪除第一個結點


如果我們想刪除第一個結點,策略會有所不同。

正如之前所提到的,我們使用頭結點 head 來表示鏈表。我們的頭是下面示例中的黑色結點 23。

如果想要刪除第一個結點,我們可以簡單地將下一個結點分配給 head。也就是說,刪除之后我們的頭將會是結點 6。

鏈表從頭結點開始,因此結點 23 不再在我們的鏈表中。

刪除最后一個結點呢?我們還能使用類似的策略嗎?

 

 

鏈表中的雙指針
讓我們從一個經典問題開始:

給定一個鏈表,判斷鏈表中是否有環。

你可能已經使用哈希表提出了解決方案。但是,使用雙指針技巧有一個更有效的解決方案。在閱讀接下來的內容之前,試着自己仔細考慮一下。

想象一下,有兩個速度不同的跑步者。如果他們在直路上行駛,快跑者將首先到達目的地。但是,如果它們在圓形跑道上跑步,那么快跑者如果繼續跑步就會追上慢跑者。

這正是我們在鏈表中使用兩個速度不同的指針時會遇到的情況:

如果沒有環,快指針將停在鏈表的末尾。
如果有環,快指針最終將與慢指針相遇。
所以剩下的問題是:

這兩個指針的適當速度應該是多少?

一個安全的選擇是每次移動慢指針一步,而移動快指針兩步。每一次迭代,快速指針將額外移動一步。如果環的長度為 M,經過 M 次迭代后,快指針肯定會多繞環一周,並趕上慢指針。

那其他選擇呢?它們有用嗎?它們會更高效嗎?
環形鏈表
給定一個鏈表,判斷鏈表中是否有環。

為了表示給定鏈表中的環,我們使用整數 pos 來表示鏈表尾連接到鏈表中的位置(索引從 0 開始)。 如果 pos 是 -1,則在該鏈表中沒有環。

輸入:head = [3,2,0,-4], pos = 1
輸出:true
解釋:鏈表中有一個環,其尾部連接到第二個節點。

輸入:head = [1,2], pos = 0
輸出:true
解釋:鏈表中有一個環,其尾部連接到第一個節點。
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def hasCycle(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
#         使用追逐法(雙指針),fast每次走兩步,slow每次走一步
        if head==None or head.next==None:
            return False
        fast=head.next
        slow=head
        while fast!=None:
            if fast==slow:
                return True
            fast=fast.next
            if fast==None:
                return False
            fast=fast.next
            slow=slow.next
        return False
環形鏈表 II
給定一個鏈表,返回鏈表開始入環的第一個節點。 如果鏈表無環,則返回 null。

為了表示給定鏈表中的環,我們使用整數 pos 來表示鏈表尾連接到鏈表中的位置(索引從 0 開始)。 如果 pos 是 -1,則在該鏈表中沒有環。

說明:不允許修改給定的鏈表。
輸入:head = [3,2,0,-4], pos = 1
輸出:tail connects to node index 1
解釋:鏈表中有一個環,其尾部連接到第二個節點。

輸入:head = [1,2], pos = 0
輸出:tail connects to node index 0
解釋:鏈表中有一個環,其尾部連接到第一個節點。


輸入:head = [1], pos = -1
輸出:no cycle
解釋:鏈表中沒有環。
 
         
 
        
https://blog.csdn.net/Sun_White_Boy/article/details/82845791?utm_source=blogxgwz3
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def detectCycle(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        if head == None or head.next == None:
            return None
        slow = fast = head
        while fast and fast.next:
            slow = slow.next
            fast = fast.next.next
            if slow == fast:
                tmp = head
                while tmp != fast:
                    tmp,fast = tmp.next,fast.next
                return tmp
        return None
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def detectCycle(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
#思路:利用set進行存儲head節點對象(地址),當地址重復時,即遇到重復環
        head_set=set()
        while head:
            if head in head_set:
                return head
            else:
                head_set.add(head)
            head=head.next
        return None
相交鏈表

編寫一個程序,找到兩個單鏈表相交的起始節點。

如下面的兩個鏈表:

在節點 c1 開始相交。

示例 1:

輸入:intersectVal = 8, listA = [4,1,8,4,5], listB = [5,0,1,8,4,5], skipA = 2, skipB = 3
輸出:Reference of the node with value = 8
輸入解釋:相交節點的值為 8 (注意,如果兩個列表相交則不能為 0)。從各自的表頭開始算起,鏈表 A 為 [4,1,8,4,5],鏈表 B 為 [5,0,1,8,4,5]。在 A 中,相交節點前有 2 個節點;在 B 中,相交節點前有 3 個節點。

 

示例 2:

輸入:intersectVal = 2, listA = [0,9,1,2,4], listB = [3,2,4], skipA = 3, skipB = 1
輸出:Reference of the node with value = 2
輸入解釋:相交節點的值為 2 (注意,如果兩個列表相交則不能為 0)。從各自的表頭開始算起,鏈表 A 為 [0,9,1,2,4],鏈表 B 為 [3,2,4]。在 A 中,相交節點前有 3 個節點;在 B 中,相交節點前有 1 個節點。

 

示例 3:

輸入:intersectVal = 0, listA = [2,6,4], listB = [1,5], skipA = 3, skipB = 2
輸出:null
輸入解釋:從各自的表頭開始算起,鏈表 A 為 [2,6,4],鏈表 B 為 [1,5]。由於這兩個鏈表不相交,所以 intersectVal 必須為 0,而 skipA 和 skipB 可以是任意值。
解釋:這兩個鏈表不相交,因此返回 null。
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def getIntersectionNode(self, headA, headB):
        """
        :type head1, head1: ListNode
        :rtype: ListNode
        """
#         思路:與上一題相似,利用set進行存儲A每個節點的地址,通過遍歷B,當節點存在是,return其節點的取值
        headA_set=set()
        while headA:
            headA_set.add(headA)
            headA=headA.next
        while headB:
            if headB in headA_set:
                return headB
            headB=headB.next
        return None

 

刪除鏈表的倒數第N個節點

給定一個鏈表,刪除鏈表的倒數第 n 個節點,並且返回鏈表的頭結點。

示例:

給定一個鏈表: 1->2->3->4->5, 和 n = 2.

當刪除了倒數第二個節點后,鏈表變為 1->2->3->5.
說明:

給定的 n 保證是有效的。

進階:

你能嘗試使用一趟掃描實現嗎?
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def removeNthFromEnd(self, head, n):
        """
        :type head: ListNode
        :type n: int
        :rtype: ListNode
#     使用前后索引(雙索引),這個的寫法是,用一條像是繩子一樣的。
# |----------|
# slow     fast
# 讓fast走n步。
# 然后fast和slow一起走,等fast.next是None,也就是到頭了。那么slow就是要刪除的點的前一個了。
# 直接把slow.next與slow.next.next結合就達標了。
# 如果走了n步后fast直接是None了。那么說明刪除的節點是head,那么返回 head.next就好了。        

""" first=head tail=head while first: #表明tail 與first之間的距離達到了n,且tail.next==None,表明first的下一位為要刪除的元素 if tail.next==None and n==0: first.next=first.next.next return head #n!=0,表明該沒有tail與first之間的距離還沒有達到n,就停止了,head已經遍歷完,那么其刪除的元素為其第一個元素 elif tail.next==None and n!=0: return head.next #tail與first之間間距為n if n!=0: tail=tail.next n-=1 continue #滿足間距之后,first與tail一起移動 first=first.next tail=tail.next return None

 

反轉鏈表
一種解決方案是按原始順序迭代結點,並將它們逐個移動到列表的頭部。似乎很難理解。我們先用一個例子來說明我們的算法
算法概述
讓我們看一個例子:

請記住,黑色結點 23 是原始的頭結點。

1. 首先,我們將黑色結點的下一個結點(即結點 6)移動到列表的頭部:

2. 然后,我們將黑色結點的下一個結點(即結點 15)移動到列表的頭部:

3. 黑色結點的下一個結點現在是空。因此,我們停止這一過程並返回新的頭結點 15。

在該算法中,每個結點只移動一次

因此,時間復雜度為 O(N),其中 N 是鏈表的長度。我們只使用常量級的額外空間,所以空間復雜度為 O(1)。

 

反轉鏈表
反轉一個單鏈表。

示例:

輸入: 1->2->3->4->5->NULL
輸出: 5->4->3->2->1->NULL
進階:
你可以迭代或遞歸地反轉鏈表。你能否用兩種方法解決這道題?
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def reverseList(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
#       迭代的方法
        prev = None
        while head:
            cur = head
            head = head.next
            cur.next = prev
            prev = cur
        return prev

 

移除鏈表元素
刪除鏈表中等於給定值 val 的所有節點。

示例:

輸入: 1->2->6->3->4->5->6, val = 6
輸出: 1->2->3->4->5
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def removeElements(self, head, val):
        """
        :type head: ListNode
        :type val: int
        :rtype: ListNode
        """
#         思路:刪除頭結點為val的節點(保證上一個節點不等於val),這樣好進行下面刪除的操作.記錄上一個節點為prev,記錄當前節點為cur,進行判斷,如果cur.val與val相等,則利用上一個節點連接當前節點的下一個節點.
        if head is None :
            return None
        while head.val == val:
            head = head.next
            if head is None:
                return None
        prev = head
        cur = head.next
        while cur != None:
            if cur.val == val:
                cur = cur.next
                prev.next = cur
            else:
                prev, cur = cur, cur.next
        return head
奇偶鏈表
給定一個單鏈表,把所有的奇數節點和偶數節點分別排在一起。請注意,這里的奇數節點和偶數節點指的是節點編號的奇偶性,而不是節點的值的奇偶性。

請嘗試使用原地算法完成。你的算法的空間復雜度應為 O(1),時間復雜度應為 O(nodes),nodes 為節點總數。

示例 1:

輸入: 1->2->3->4->5->NULL
輸出: 1->3->5->2->4->NULL
示例 2:

輸入: 2->1->3->5->6->4->7->NULL 
輸出: 2->3->6->7->1->5->4->NULL
說明:

應當保持奇數節點和偶數節點的相對順序。
鏈表的第一個節點視為奇數節點,第二個節點視為偶數節點,以此類推。
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def oddEvenList(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        #思路:將鏈表分為兩部分,最后進行連接
        if head == None or head.next == None:
            return head
                slow=head
        fast=head.next
        t=fast
        #得保證兩者的下一位都不為None,否則,會出現,None.next
        while  slow.next and fast.next:
            slow.next=fast.next
            slow=slow.next
            fast.next=slow.next
            fast=fast.next
        slow.next=t
        return head
回文鏈表
請判斷一個鏈表是否為回文鏈表。

示例 1:

輸入: 1->2
輸出: false
示例 2:

輸入: 1->2->2->1
輸出: true
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def isPalindrome(self, head):
        """
        :type head: ListNode
        :rtype: bool
        """
#         思路:遍歷鏈表,使用list存儲,if not head :
            return True
        temp_list=[]
        while head:
            temp_list.append(head.val)
            head=head.next
        return temp_list==temp_list[::-1]
合並兩個有序鏈表
將兩個有序鏈表合並為一個新的有序鏈表並返回。新鏈表是通過拼接給定的兩個鏈表的所有節點組成的。 

示例:

輸入:1->2->4, 1->3->4
輸出:1->1->2->3->4->4

 

# Definition for singly-linked list.
# class ListNode:
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution:
    def mergeTwoLists(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
        new_head=ListNode(0)
        pre=new_head
        while l1 and l2:
            if l1.val<=l2.val:
                pre.next=l1
                l1=l1.next
            else:
                pre.next=l2
                l2=l2.next
            pre=pre.next
        if l1==None:
            pre.next=l2
        if l2==None:
            pre.next=l1
        return new_head.next
兩數相加
給出兩個 非空 的鏈表用來表示兩個非負的整數。其中,它們各自的位數是按照 逆序 的方式存儲的,並且它們的每個節點只能存儲 一位 數字。

如果,我們將這兩個數相加起來,則會返回一個新的鏈表來表示它們的和。

您可以假設除了數字 0 之外,這兩個數都不會以 0 開頭。

示例:

輸入:(2 -> 4 -> 3) + (5 -> 6 -> 4)
輸出:7 -> 0 -> 8
原因:342 + 465 = 807
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def addTwoNumbers(self, l1, l2):
        """
        :type l1: ListNode
        :type l2: ListNode
        :rtype: ListNode
        """
       #思路:將鏈表轉化為list進行操作
        int_l1=''
        int_l2=''
        while l1:
            int_l1+=str(l1.val)
            l1=l1.next
        while l2:
            int_l2+=str(l2.val)
            l2=l2.next
        int_l1=int(int_l1[::-1])
        int_l2=int(int_l2[::-1])
        sum_=(int_l1+int_l2)
        nu=sum_%10
        sum_=sum_/10
        k=ListNode(nu)
        head=k
        while sum_:
            nu=sum_%10
            sum_=sum_/10
            head.next=ListNode(nu)
            head=head.next
        return k
        

 

 

練習題:
83. 刪除排序鏈表中的重復元素
給定一個排序鏈表,刪除所有重復的元素,使得每個元素只出現一次。

示例 1:

輸入: 1->1->2
輸出: 1->2
示例 2:

輸入: 1->1->2->3->3
輸出: 1->2->3
https://leetcode-cn.com/problems/remove-duplicates-from-sorted-list/
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def deleteDuplicates(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
        if head==None:
            return []
        prev=head
        next_=head.next
        while next_:
            if prev.val==next_.val:
                prev.next=next_.next
                next_=next_.next
                continue
            prev=next_
            next_=next_.next
        return head
                

 

876. 鏈表的中間結點
給定一個帶有頭結點 head 的非空單鏈表,返回鏈表的中間結點。

如果有兩個中間結點,則返回第二個中間結點。

 

示例 1:

輸入:[1,2,3,4,5]
輸出:此列表中的結點 3 (序列化形式:[3,4,5])
返回的結點值為 3 。 (測評系統對該結點序列化表述是 [3,4,5])。
注意,我們返回了一個 ListNode 類型的對象 ans,這樣:
ans.val = 3, ans.next.val = 4, ans.next.next.val = 5, 以及 ans.next.next.next = NULL.
示例 2:

輸入:[1,2,3,4,5,6]
輸出:此列表中的結點 4 (序列化形式:[4,5,6])
由於該列表有兩個中間結點,值分別為 3 和 4,我們返回第二個結點。
 

提示:

給定鏈表的結點數介於 1 和 100 之間。
https://leetcode-cn.com/problems/middle-of-the-linked-list/
# Definition for singly-linked list.
# class ListNode(object):
#     def __init__(self, x):
#         self.val = x
#         self.next = None

class Solution(object):
    def middleNode(self, head):
        """
        :type head: ListNode
        :rtype: ListNode
        """
#         思路:先進行遍歷其長度,下一步直接遍歷其元素
        first=head
        head_len=0
        while first:
            head_len+=1
            first=first.next
        head_2=head_len/2
        first=head
        while head_2:
            head_2-=1
            first=first.next
        return first

 

 

 

使用雙指針解決鏈表問題提示
它與我們在數組中學到的內容類似。但它可能更棘手而且更容易出錯。你應該注意以下幾點:

1. 在調用 next 字段之前,始終檢查節點是否為空。

獲取空節點的下一個節點將導致空指針錯誤。例如,在我們運行 fast = fast.next.next 之前,需要檢查 fast 和 fast.next 不為空。

2. 仔細定義循環的結束條件。

運行幾個示例,以確保你的結束條件不會導致無限循環。在定義結束條件時,你必須考慮我們的第一點提示。

復雜度分析
空間復雜度分析容易。如果只使用指針,而不使用任何其他額外的空間,那么空間復雜度將是 O(1)。但是,時間復雜度的分析比較困難。為了得到答案,我們需要分析運行循環的次數。

在前面的查找循環示例中,假設我們每次移動較快的指針 2 步,每次移動較慢的指針 1 步。

如果沒有循環,快指針需要 N/2 次才能到達鏈表的末尾,其中 N 是鏈表的長度。
如果存在循環,則快指針需要 M 次才能趕上慢指針,其中 M 是列表中循環的長度。
顯然,M <= N 。所以我們將循環運行 N 次。對於每次循環,我們只需要常量級的時間。因此,該算法的時間復雜度總共為 O(N)。

自己分析其他問題以提高分析能力。別忘了考慮不同的條件。如果很難對所有情況進行分析,請考慮最糟糕的情況。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM