- 連結串列基礎
- 任務
- 203. 移除連結串列元素
- 思路
- 707. 設計連結串列
- 思路
- 206. 反轉連結串列
- 思路 利用虛擬頭節點依次遍歷處理即可,注意遍歷後處理原來的head節點,指向為None而不是統一操作後的指向的dummy。
- 203. 移除連結串列元素
- 總結
連結串列基礎
單連結串列節點中儲存著它的值和它的後繼節點。在連結串列中,若知道插入刪除位置,由於不需要像陣列那樣大量的移動元素,而只是修改幾個節點的後繼,插入刪除的時間複雜度為O(1),而查詢只能遍歷連結串列,所以查詢的時間複雜度為O(n)
#單連結串列節點
class ListNode():
def __init__(self,val = 0,next = None):
self.val = val
self.next = next
任務
203. 移除連結串列元素
給你一個連結串列的頭節點 head 和一個整數 val ,請你刪除連結串列中所有滿足 Node.val == val 的節點,並返回新的頭節點。
思路
對於滿足要求的頭節點外的任意節點,刪除邏輯是將該節點的前一個節點的後繼修改為指向該節點的後繼。但是當頭節點滿足要求時,就直接將head指向下一個節點。為了統一所有節點的邏輯,特引入虛擬節點dummy,它是頭節點的前一個節點,此時所有滿足要求的節點的邏輯就可以統一。具體邏輯為沒找到符合要求的元素時,prev和cur均向後移動,找到時,走單個節點的刪除邏輯,並且cur自然向後移動一位,繼續後續處理。
class Solution:
def removeElements(self, head: Optional[ListNode], val: int) -> Optional[ListNode]:
if head == None:
return None
dummy = ListNode()
dummy.next = head
prev,cur = dummy,head
while cur!=None:
if cur.val == val:
prev.next = cur.next
else:
prev = prev.next
cur = cur.next
return dummy.next
還有一種寫法是用一個遍歷指標,比如cur,然後使用類似cur->next.val == val 的語句判斷,符合時用 cur->next = cur->next->next 刪除單個元素,不符合時cur向後移動即可,這種方式更加的簡潔,但是迴圈條件是cur!=None and cur.next!=None, 可能也是長時間沒有刷題,個人會本能的使用多加變數讓邏輯更直接的方法,即上述加一個prev指標的邏輯,覺得邏輯上比較清晰,比如迴圈條件,由於該題比較簡單,所以使用哪種都可以。
707. 設計連結串列
- 實現 MyLinkedList 類:
- MyLinkedList() 初始化 MyLinkedList 物件。
- int get(int index) 獲取連結串列中下標為 index 的節點的值。如果下標無效,則返回 -1 。
- void addAtHead(int val) 將一個值為 val 的節點插入到連結串列中第一個元素之前。在插入完成後,新節點會成為連結串列的第一個節點。
- void addAtTail(int val) 將一個值為 val 的節點追加到連結串列中作為連結串列的最後一個元素。
- void addAtIndex(int index, int val) 將一個值為 val 的節點插入到連結串列中下標為 index 的節點之前。如果 index 等於連結串列的長度,那麼該節點會被追加到連結串列的末尾。如果 index 比長度更大,該節點將 不會插入 到連結串列中。
- void deleteAtIndex(int index) 如果下標有效,則刪除連結串列中下標為 index 的節點。
思路
由於頭節點可以由虛擬頭節點得到,為了不冗餘維護兩個關聯的資訊,這裡使用dummyhead維護連結串列的虛擬頭節點。注意邊界條件特別是插入位置為連結串列的後一個時(即index == size時)的特殊處理。這裡有些邏輯寫的不是很直觀的原因是沒有維護連結串列的長度,因此在一些邊界條件的處理需要特別的注意,後續有時間補上維護連結串列長度的寫法,迴圈邏輯會更簡單(不用在迴圈邏輯中考慮越界問題),但是有修改size的地方需要修改。
class ListNode():
def __init__(self,val = 0,next = None):
self.val = val
self.next = next
class MyLinkedList:
def __init__(self):
self.dummyhead = ListNode()
def get(self, index: int) -> int:
if index < 0:
return -1
cur = self.dummyhead.next
while cur:
if index == 0:
return cur.val
index-=1
cur=cur.next
return -1
def addAtHead(self, val: int) -> None:
newNode = ListNode(val,self.dummyhead.next)
self.dummyhead.next = newNode
def addAtTail(self, val: int) -> None:
prev = self.dummyhead
cur = self.dummyhead.next
while cur:
prev= cur
cur = cur.next
newNode = ListNode(val)
prev.next = newNode
def addAtIndex(self, index: int, val: int) -> None:
if index < 0:
return
prev = self.dummyhead
cur = self.dummyhead.next
while prev:
if cur==None and index >0: break
if index == 0:
newNode = ListNode(val,cur)
prev.next = newNode
break
else:
index-=1
cur = cur.next
prev = prev.next
def deleteAtIndex(self, index: int) -> None:
prev = self.dummyhead
cur = self.dummyhead.next
while cur:
if index == 0:
prev.next = cur.next
break
else:
index-=1
cur = cur.next
prev = prev.next
# Your MyLinkedList object will be instantiated and called as such:
# obj = MyLinkedList()
# param_1 = obj.get(index)
# obj.addAtHead(val)
# obj.addAtTail(val)
# obj.addAtIndex(index,val)
# obj.deleteAtIndex(index)
206. 反轉連結串列
給你單連結串列的頭節點 head ,請你反轉連結串列,並返回反轉後的連結串列。
思路 利用虛擬頭節點依次遍歷處理即可,注意遍歷後處理原來的head節點,指向為None而不是統一操作後的指向的dummy。
class Solution:
def reverseList(self, head: Optional[ListNode]) -> Optional[ListNode]:
if not head:
return None
dummy = ListNode()
dummy.next = head
prev = dummy
cur = dummy.next
while cur:
tmp = cur.next
cur.next = prev
prev = cur
cur = tmp
head.next = None
head = prev
return head
總結
今天的任務是對於連結串列中單個或者多個節點的增刪查,這類問題不涉及像陣列中一些問題的演算法思想,只要按照題目描述去遍歷處理即可,難點在於迴圈條件和單次遍歷的指標變化,引入dummy頭節點方便統一邏輯,注意遍歷連結串列時的邊界條件,以及單次處理時next域的處理和指標的正確處理。思考時儘量做到一次做對,起碼要將大多數普通節點的邏輯做對,這類題目很容易在邊界出錯,遇到特殊的節點或沒考慮到的問題出錯了也不用慌,除錯和畫圖分析即可解決(如707. 設計連結串列 中插入index位置 index為size時,如果迴圈條件為cur則到達插入位置時無法進入迴圈,因此迴圈條件為prev 或迴圈條件不變在迴圈退出後再特殊處理一次也可)