day3 連結串列-增刪查

haohaoscnblogs發表於2024-07-19

目錄
  • 連結串列基礎
  • 任務
    • 203. 移除連結串列元素
      • 思路
    • 707. 設計連結串列
      • 思路
    • 206. 反轉連結串列
      • 思路 利用虛擬頭節點依次遍歷處理即可,注意遍歷後處理原來的head節點,指向為None而不是統一操作後的指向的dummy。
  • 總結

連結串列基礎

單連結串列節點中儲存著它的值和它的後繼節點。在連結串列中,若知道插入刪除位置,由於不需要像陣列那樣大量的移動元素,而只是修改幾個節點的後繼,插入刪除的時間複雜度為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 或迴圈條件不變在迴圈退出後再特殊處理一次也可)

相關文章