單鏈表中的每個結點不僅包含值,還包含鏈接到下一個結點的引用字段
。通過這種方式,單鏈表將所有結點按順序組織起來。、
下面是一個單鏈表的例子:
鏈表有兩種類型:單鏈表和雙鏈表。上面給出的例子是一個單鏈表,這里有一個雙鏈表的例子:
我們將在接下來的章節中介紹更多內容。完成這張卡片后,你將:
- 了解單鏈表和雙鏈表的結構;
- 在單鏈表或雙鏈表中實現遍歷、插入和刪除;
- 分析在單鏈表或雙鏈表中的各種操作的復雜度;
- 在鏈表中使用雙指針技巧(快指針慢指針技巧);
- 解決一些經典問題,例如反轉鏈表;
- 分析你設計的算法的復雜度;
- 積累設計和調試的經驗。
單鏈表中的每個結點不僅包含值,還包含鏈接到下一個結點的引用字段
。通過這種方式,單鏈表將所有結點按順序組織起來。、
下面是一個單鏈表的例子:
藍色箭頭顯示單個鏈接列表中的結點是如何組合在一起的。
在大多數情況下,我們將使用頭結點(第一個結點)來表示整個列表。
操作
與數組不同,我們無法在常量時間內訪問單鏈表中的隨機元素。 如果我們想要獲得第 i 個元素,我們必須從頭結點逐個遍歷。 我們按索引
來訪問元素
平均要花費 O(N)
時間,其中 N 是鏈表的長度。
例如,在上面的示例中,頭結點是 23。訪問第 3 個結點的唯一方法是使用頭結點中的“next”字段到達第 2 個結點(結點 6); 然后使用結點 6 的“next”字段,我們能夠訪問第 3 個結點。
你可能想知道為什么鏈表很有用,盡管它在通過索引訪問數據時(與數組相比)具有如此糟糕的性能。 在接下來的兩篇文章中,我們將介紹插入和刪除操作,你將了解到鏈表的好處。
如果我們想在給定的結點 prev
之后添加新值,我們應該:
- 使用給定值初始化新結點
cur;
- 將
cur
的“next”字段鏈接到 prev 的下一個結點next
; - 將
prev
中的“next”字段鏈接到cur
。
與數組不同,我們不需要將所有元素移動到插入元素之后。因此,您可以在 O(1)
時間復雜度中將新結點插入到鏈表中,這非常高效。
示例
讓我們在第二個結點 6 之后插入一個新的值 9。
我們將首先初始化一個值為 9 的新結點。然后將結點 9 鏈接到結點 15。最后,將結點 6 鏈接到結點 9。
插入之后,我們的鏈表將如下所示:
在開頭添加結點
眾所周知,我們使用頭結點來代表整個列表。
因此,在列表開頭添加新節點時更新頭結點 head
至關重要。
- 初始化一個新結點
cur;
- 將新結點鏈接到我們的原始頭結點
head
。 - 將
cur
指定為head
。
例如,讓我們在列表的開頭添加一個新結點 9。
- 我們初始化一個新結點 9 並將其鏈接到當前頭結點 23。
- 指定結點 9 為新的頭結點。
如何在列表的末尾添加新的結點?我們還能使用類似的策略嗎?
如果我們想從單鏈表中刪除現有結點 cur
,可以分兩步完成:
- 找到 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)。 自己分析其他問題以提高分析能力。別忘了考慮不同的條件。如果很難對所有情況進行分析,請考慮最糟糕的情況。