【DW1月-力扣刷題】Task01 鏈表

參考鏈接:https://github.com/itcharge/LeetCode-Py

一、鏈表基礎(chǔ)知識(shí)

1.1 鏈表簡(jiǎn)介

鏈表(Linked List):是一種線(xiàn)性表數(shù)據(jù)結(jié)構(gòu)。它使用一組任意的存儲(chǔ)單元(可以是連續(xù)的,也可以是不連續(xù)的),來(lái)存儲(chǔ)一組具有相同類(lèi)型的數(shù)據(jù)。
簡(jiǎn)單來(lái)說(shuō),鏈表 是實(shí)現(xiàn)線(xiàn)性表的鏈?zhǔn)酱鎯?chǔ)結(jié)構(gòu)的基礎(chǔ)。

鏈表結(jié)構(gòu)優(yōu)缺點(diǎn):

  • 優(yōu)點(diǎn):存儲(chǔ)空間不必事先分配,在需要存儲(chǔ)空間的時(shí)候可以臨時(shí)申請(qǐng),不會(huì)造成空間的浪費(fèi);一些操作的時(shí)間效率遠(yuǎn)比數(shù)組高(插入、移動(dòng)、刪除元素等)。
  • 缺點(diǎn):不僅數(shù)據(jù)元素本身的數(shù)據(jù)信息要占用存儲(chǔ)空間,指針也需要占用存儲(chǔ)空間,鏈表結(jié)構(gòu)比數(shù)組結(jié)構(gòu)的空間開(kāi)銷(xiāo)大。

鏈表種類(lèi):

  • 單鏈表。單鏈表通過(guò)將一組任意的存儲(chǔ)單元串聯(lián)在一起。其中,每個(gè)數(shù)據(jù)元素占用若干存儲(chǔ)單元的組合稱(chēng)為一個(gè)「鏈節(jié)點(diǎn)」。
  • 雙鏈表。它的每個(gè)鏈節(jié)點(diǎn)中有兩個(gè)指針,分別指向直接后繼和直接前驅(qū)。
  • 循環(huán)鏈表。它的最后一個(gè)鏈節(jié)點(diǎn)指向頭節(jié)點(diǎn),形成一個(gè)環(huán)。

1.2 鏈表的基本操作

1.2.1 鏈表的結(jié)構(gòu)定義

  • 鏈表是由鏈節(jié)點(diǎn)通過(guò) next 鏈接而構(gòu)成的,所以先來(lái)定義一個(gè)簡(jiǎn)單的鏈節(jié)點(diǎn)類(lèi),即ListNode 類(lèi)。ListNode 類(lèi)使用成員變量 val 表示數(shù)據(jù)元素的值,使用指針變量 next 表示后繼指針。
  • 然后再定義鏈表類(lèi),即 LinkedList 類(lèi)。ListkedList 類(lèi)中只有一個(gè)鏈節(jié)點(diǎn)變量 head 用來(lái)表示鏈表的頭節(jié)點(diǎn)。

代碼如下:

# 鏈節(jié)點(diǎn)類(lèi)
class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next

# 鏈表類(lèi)
class LinkedList:
    def __init__(self):
        self.head = None

1.2.1 建立一個(gè)線(xiàn)性鏈表

建立一個(gè)線(xiàn)性鏈表就是根據(jù)線(xiàn)性表的數(shù)據(jù)元素動(dòng)態(tài)生成鏈節(jié)點(diǎn),并依次將其連接到鏈表中。其做法如下:

  • 從所給線(xiàn)性表的第 1 個(gè)數(shù)據(jù)元素開(kāi)始依次獲取表中的數(shù)據(jù)元素。
  • 每獲取一個(gè)數(shù)據(jù)元素,就為該數(shù)據(jù)元素生成一個(gè)新節(jié)點(diǎn),將新節(jié)點(diǎn)插入到鏈表的尾部。
  • 插入完畢之后返回第 1 個(gè)鏈節(jié)點(diǎn)的地址。

建立一個(gè)線(xiàn)性鏈表的時(shí)間復(fù)雜度為 O(n),n 為線(xiàn)性表長(zhǎng)度。
代碼如下:

# 根據(jù) data 初始化一個(gè)新鏈表
def create(self, data):
    self.head = ListNode(0)
    cur = self.head
    for i in range(len(data)):
        node = ListNode(data[i])
        cur.next = node
        cur = cur.next

1.2.3 求線(xiàn)性鏈表的長(zhǎng)度

線(xiàn)性鏈表的長(zhǎng)度被定義為鏈表中包含的鏈節(jié)點(diǎn)的個(gè)數(shù)。求線(xiàn)性鏈表的長(zhǎng)度操作只需要使用一個(gè)可以順著鏈表指針移動(dòng)的指針變量 cur 和一個(gè)計(jì)數(shù)器 count。具體做法如下:

  • 讓指針變量 cur 指向鏈表的第 1 個(gè)鏈節(jié)點(diǎn)。
  • 然后順著鏈節(jié)點(diǎn)的 next 指針遍歷鏈表,指針變量 cur 每指向一個(gè)鏈節(jié)點(diǎn),計(jì)數(shù)器就做一次計(jì)數(shù)。
  • 等 cur 指向?yàn)榭諘r(shí)結(jié)束遍歷,此時(shí)計(jì)數(shù)器的數(shù)值就是鏈表的長(zhǎng)度,將其返回即可。

求線(xiàn)性鏈表的長(zhǎng)度操作的問(wèn)題規(guī)模是鏈表的鏈節(jié)點(diǎn)數(shù) n,基本操作是 cur 指針的移動(dòng),操作的次數(shù)為 n,因此算法的時(shí)間復(fù)雜度為 O(n)
代碼如下:

# 獲取鏈表長(zhǎng)度
def length(self):
    count = 0
    cur = self.head
    while cur:
        count += 1
        cur = cur.next 
    return count

1.2.4 查找元素

在鏈表中查找值為 val 的位置:鏈表不能像數(shù)組那樣進(jìn)行隨機(jī)訪(fǎng)問(wèn),只能從頭節(jié)點(diǎn) head 開(kāi)始,沿著鏈表一個(gè)一個(gè)節(jié)點(diǎn)逐一進(jìn)行查找。如果查找成功,返回被查找節(jié)點(diǎn)的地址。否則返回 None。

查找元素操作的問(wèn)題規(guī)模是鏈表的長(zhǎng)度 n,而基本操作是指針 cur 的移動(dòng)操作,所以查找元素算法的時(shí)間復(fù)雜度為 O(n)

# 查找元素
def find(self, val):
    cur = self.head
    while cur:
        if val == cur.val:
            return cur
        cur = cur.next

    return None

1.2.5 插入元素

鏈表中插入元素操作分為三種:

  • 鏈表頭部插入元素:在鏈表第 1 個(gè)鏈節(jié)點(diǎn)之前插入值為 val 的鏈節(jié)點(diǎn)。
  • 鏈表尾部插入元素:在鏈表最后 1 個(gè)鏈節(jié)點(diǎn)之后插入值為 val 的鏈節(jié)點(diǎn)。
  • 鏈表中間插入元素:在鏈表第 i 個(gè)鏈節(jié)點(diǎn)之前插入值為 val 的鏈節(jié)點(diǎn)。

鏈表頭部插入元素
算法實(shí)現(xiàn)的步驟如下:

  • 先創(chuàng)建一個(gè)值為 val 的鏈節(jié)點(diǎn) node。
  • 然后將 node 的 next 指針指向鏈表的頭節(jié)點(diǎn) head。
  • 再將鏈表的頭節(jié)點(diǎn) head 指向 node。


    image.png

因?yàn)樵阪湵眍^部插入鏈節(jié)點(diǎn)與鏈表的長(zhǎng)度無(wú)關(guān),所以該算法的時(shí)間復(fù)雜度為 O(1)

# 頭部插入元素
def insertFront(self, val):
    node = ListNode(val)
    node.next = self.head
    self.head = node

尾部插入元素
算法實(shí)現(xiàn)的步驟如下:

  • 先創(chuàng)建一個(gè)值為 val 的鏈節(jié)點(diǎn) node。
  • 使用指針 cur 指向鏈表的頭節(jié)點(diǎn) head。
  • 通過(guò)鏈節(jié)點(diǎn)的 next 指針移動(dòng) cur 指針,從而遍歷鏈表,直到 cur.next == None。
  • 令 cur.next 指向?qū)⑿碌逆湽?jié)點(diǎn) node。
    image.png

    因?yàn)閷?cur 從鏈表頭部移動(dòng)到尾部的操作次數(shù)是 n 次,所以該算法的時(shí)間復(fù)雜度是 O(n)
# 尾部插入元素
def insertRear(self, val):
    node = ListNode(val)
    cur = self.head
    while cur.next:
        cur = cur.next
    cur.next = node

中間插入元素
算法的實(shí)現(xiàn)步驟如下:

  • 使用指針變量 cur 和一個(gè)計(jì)數(shù)器 count。令 cur 指向鏈表的頭節(jié)點(diǎn),count 初始值賦值為 0。
  • 沿著鏈節(jié)點(diǎn)的 next 指針遍歷鏈表,指針變量 cur 每指向一個(gè)鏈節(jié)點(diǎn),計(jì)數(shù)器就做一次計(jì)數(shù)。
  • 當(dāng) count == index - 1 時(shí),說(shuō)明遍歷到了第 index - 1 個(gè)鏈節(jié)點(diǎn),此時(shí)停止遍歷。
  • 創(chuàng)建一個(gè)值為 val 的鏈節(jié)點(diǎn) node。
  • 將 node.next 指向 cur.next。
  • 然后令 cur.next 指向 node。
    image.png

    因?yàn)閷?cur 從鏈表頭部移動(dòng)到第 i 個(gè)鏈節(jié)點(diǎn)之前的操作平均時(shí)間復(fù)雜度是 O(n),所以該算法的時(shí)間復(fù)雜度是 O(n)
# 中間插入元素
def insertInside(self, index, val):
    count = 0
    cur = self.head
    while cur and count < index - 1:
        count += 1
        cur = cur.next
        
    if not cur:
        return 'Error'
    
    node = ListNode(val)
    node.next = cur.next
    cur.next = node

1.2.6 改變?cè)?/h3>

將鏈表中第 i 個(gè)元素值改為 val:首先要先遍歷到第 i 個(gè)鏈節(jié)點(diǎn),然后直接更改第 i 個(gè)鏈節(jié)點(diǎn)的元素值。具體做法如下:

  • 使用指針變量 cur 和一個(gè)計(jì)數(shù)器 count。令 cur 指向鏈表的頭節(jié)點(diǎn),count 初始值賦值為 0。
  • 沿著鏈節(jié)點(diǎn)的 next 指針遍歷鏈表,指針變量 cur 每指向一個(gè)鏈節(jié)點(diǎn),計(jì)數(shù)器就做一次計(jì)數(shù)。
  • 當(dāng) count == index 時(shí),說(shuō)明遍歷到了第 index 個(gè)鏈節(jié)點(diǎn),此時(shí)停止遍歷。
  • 直接更改 cur 的值 val。

因?yàn)閷?cur 從鏈表頭部移動(dòng)到第 i 個(gè)鏈節(jié)點(diǎn)的操作平均時(shí)間復(fù)雜度是 O(n),所以該算法的時(shí)間復(fù)雜度是 O(n)

# 改變?cè)?def change(self, index, val):
    count = 0
    cur = self.head
    while cur and count < index:
        count += 1
        cur = cur.next
        
    if not cur:
        return 'Error'
    
    cur.val = val

1.2.7 刪除元素

鏈表的刪除元素操作同樣分為三種情況:

  • 鏈表頭部刪除元素:刪除鏈表的第 1 個(gè)鏈節(jié)點(diǎn)。
  • 鏈表尾部刪除元素:刪除鏈表末尾最后 1 個(gè)鏈節(jié)點(diǎn)。
  • 鏈表中間刪除元素:刪除鏈表第 i 個(gè)鏈節(jié)點(diǎn)。

二、鏈表排序

2.1 鏈表冒泡排序

算法描述:
使用三個(gè)指針 node_i、node_j 和 tail。其中 node_i 用于控制外循環(huán)次數(shù),循環(huán)次數(shù)為鏈節(jié)點(diǎn)個(gè)數(shù)(鏈表長(zhǎng)度)。node_j 和 tail 用于控制內(nèi)循環(huán)次數(shù)和循環(huán)結(jié)束位置。
算法步驟:

  • 排序開(kāi)始前,將 node_i 、node_j 置于頭節(jié)點(diǎn)位置。tail 指向鏈表末尾,即 None。
  • 比較鏈表中相鄰兩個(gè)元素 node_j.val 與 node_j.next.val 的值大小,如果 node_j.val > node_j.next.val,則值相互交換。否則不發(fā)生交換。然后向右移動(dòng) node_j 指針,直到 node_j.next == tail 時(shí)停止。
  • 一次循環(huán)之后,將 tail 移動(dòng)到 node_j 所在位置。相當(dāng)于 tail 向左移動(dòng)了一位。此時(shí) tail 節(jié)點(diǎn)右側(cè)為鏈表中最大的鏈節(jié)點(diǎn)。
  • 然后移動(dòng) node_i 節(jié)點(diǎn),并將 node_j 置于頭節(jié)點(diǎn)位置。然后重復(fù)第 3、4 步操作。
  • 直到 node_i 節(jié)點(diǎn)移動(dòng)到鏈表末尾停止,排序結(jié)束。
  • 返回鏈表的頭節(jié)點(diǎn) head。
def bubbleSort(self, head: ListNode):
    node_i = head
    tail = None
    # 外層循環(huán)次數(shù)為 鏈表節(jié)點(diǎn)個(gè)數(shù)
    while node_i:
        node_j = head
        while node_j and node_j.next != tail:
            if node_j.val > node_j.next.val:
                # 交換兩個(gè)節(jié)點(diǎn)的值
                node_j.val, node_j.next.val = node_j.next.val, node_j.val
            node_j = node_j.next
        # 尾指針向前移動(dòng) 1 位,此時(shí)尾指針右側(cè)為排好序的鏈表
        tail = node_j
        node_i = node_i.next
        
    return head

2.2 鏈表選擇排序

算法描述:

  • 使用兩個(gè)指針 node_i、node_j。node_i 既可以用于控制外循環(huán)次數(shù),又可以作為當(dāng)前未排序鏈表的第一個(gè)鏈節(jié)點(diǎn)位置。
  • 使用 min_node 記錄當(dāng)前未排序鏈表中值最小的鏈節(jié)點(diǎn)。
  • 每一趟排序開(kāi)始時(shí),先令 min_node = node_i(即暫時(shí)假設(shè)鏈表中 node_i 節(jié)點(diǎn)為值最小的節(jié)點(diǎn),經(jīng)過(guò)比較后再確定最小值節(jié)點(diǎn)位置)。
  • 然后依次比較未排序鏈表中 node_j.val 與 min_node.val 的值大小。如果 node_j.val > min_node.val,則更新 min_node 為 node_j。
  • 這一趟排序結(jié)束時(shí),未排序鏈表中最小值節(jié)點(diǎn)為 min_node,如果 node_i != min_node,則將 node_i 與 min_node 值進(jìn)行交換。如果 node_i == min_node,則不用交換。
  • 排序結(jié)束后,繼續(xù)向右移動(dòng) node_i,重復(fù)上述步驟,在剩余未排序鏈表中尋找最小的鏈節(jié)點(diǎn),并與 node_i 進(jìn)行比較和交換,直到 node_i == None 或者 node_i.next == None 時(shí),停止排序。
  • 返回鏈表的頭節(jié)點(diǎn) head。
def sectionSort(self, head: ListNode):
    node_i = head
    # node_i 為當(dāng)前未排序鏈表的第一個(gè)鏈節(jié)點(diǎn)
    while node_i and node_i.next:
        # min_node 為未排序鏈表中的值最小節(jié)點(diǎn)
        min_node = node_i
        node_j = node_i.next
        while node_j:
            if node_j.val < min_node.val:
                min_node = node_j
            node_j = node_j.next
        # 交換值最小節(jié)點(diǎn)與未排序鏈表中第一個(gè)節(jié)點(diǎn)的值
        if node_i != min_node:
            node_i.val, min_node.val = min_node.val, node_i.val
        node_i = node_i.next
    
    return head

2.3 鏈表插入排序

算法描述:

  • 1、先使用啞節(jié)點(diǎn) dummy_head 構(gòu)造一個(gè)指向 head 的指針,使得可以從 head 開(kāi)始遍歷。
  • 2、維護(hù) sorted_list 為鏈表的已排序部分的最后一個(gè)節(jié)點(diǎn),初始時(shí),sorted_list = head。
  • 3、維護(hù) prev 為插入元素位置的前一個(gè)節(jié)點(diǎn),維護(hù) cur 為待插入元素。初始時(shí),prev = head,cur = head.next。
  • 4、比較 sorted_list 和 cur 的節(jié)點(diǎn)值。
    • 如果 sorted_list.val <= cur.val,說(shuō)明 cur 應(yīng)該插入到 sorted_list 之后,則將 sorted_list 后移一位。
    • 如果 sorted_list.val > cur.val,說(shuō)明 cur 應(yīng)該插入到 head 與 sorted_list 之間。則使用 prev 從 head 開(kāi)始遍歷,直到找到插入 cur 的位置的前一個(gè)節(jié)點(diǎn)位置。然后將 cur 插入。
  • 5、令 cur = sorted_list.next,此時(shí) cur 為下一個(gè)待插入元素。
  • 重復(fù) 4、5 步驟,直到 cur 遍歷結(jié)束為空。返回 dummy_head 的下一個(gè)節(jié)點(diǎn)。
def insertionSort(self, head: ListNode):
    if not head and not head.next:
        return head
    
    dummy_head = ListNode(-1)
    dummy_head.next = head
    sorted_list = head
    cur = head.next 
    
    while cur:
        if sorted_list.val <= cur.val:
            # 將 cur 插入到 sorted_list 之后
            sorted_list = sorted_list.next 
        else:
            prev = dummy_head
            while prev.next.val <= cur.val:
                prev = prev.next
            # 將 cur 到鏈表中間
            sorted_list.next = cur.next
            cur.next = prev.next
            prev.next = cur
        cur = sorted_list.next 
    
    return dummy_head.next

2.4 鏈表歸并排序

算法描述:
分割環(huán)節(jié):找到鏈表中心鏈節(jié)點(diǎn),從中心節(jié)點(diǎn)將鏈表斷開(kāi),并遞歸進(jìn)行分割。

  • 使用快慢指針 fast = head.next、slow = head,讓 fast 每次移動(dòng) 2 步,slow 移動(dòng) 1 步,移動(dòng)到鏈表末尾,從而找到鏈表中心鏈節(jié)點(diǎn),即 slow。
  • 從中心位置將鏈表從中心位置分為左右兩個(gè)鏈表 left_head 和 right_head,并從中心位置將其斷開(kāi),即 slow.next = None。
  • 對(duì)左右兩個(gè)鏈表分別進(jìn)行遞歸分割,直到每個(gè)鏈表中只包含一個(gè)鏈節(jié)點(diǎn)。

歸并環(huán)節(jié):將遞歸后的鏈表進(jìn)行兩兩歸并,完成一遍后每個(gè)子鏈表長(zhǎng)度加倍。重復(fù)進(jìn)行歸并操作,直到得到完整的鏈表。

  • 使用啞節(jié)點(diǎn) dummy_head 構(gòu)造一個(gè)頭節(jié)點(diǎn),并使用 cur 指向 dummy_head 用于遍歷。
  • 比較兩個(gè)鏈表頭節(jié)點(diǎn) left 和 right 的值大小。將較小的頭節(jié)點(diǎn)加入到合并后的鏈表中。并向后移動(dòng)該鏈表的頭節(jié)點(diǎn)指針。
  • 然后重復(fù)上一步操作,直到兩個(gè)鏈表中出現(xiàn)鏈表為空的情況。
  • 將剩余鏈表插入到合并中的鏈表中。
  • 將啞節(jié)點(diǎn) dummy_dead 的下一個(gè)鏈節(jié)點(diǎn) dummy_head.next 作為合并后的頭節(jié)點(diǎn)返回。
def merge(self, left, right):
    # 歸并環(huán)節(jié)
    dummy_head = ListNode(-1)
    cur = dummy_head
    while left and right:
        if left.val <= right.val:
            cur.next = left
            left = left.next
        else:
            cur.next = right
            right = right.next
        cur = cur.next
    
    if left:
        cur.next = left
    elif right:
        cur.next = right
        
    return dummy_head.next
    
def mergeSort(self, head: ListNode):
    # 分割環(huán)節(jié)
    if not head or not head.next:
        return head
    
    # 快慢指針找到中心鏈節(jié)點(diǎn)
    slow, fast = head, head.next
    while fast and fast.next:
        slow = slow.next 
        fast = fast.next.next 
    
    # 斷開(kāi)左右鏈節(jié)點(diǎn)
    left_head, right_head = head, slow.next 
    slow.next = None
    
    # 歸并操作
    return self.merge(self.mergeSort(left_head), self.mergeSort(right_head))

2.5 鏈表快速排序

算法描述:

  • 從鏈表中找到一個(gè)基準(zhǔn)值 pivot,這里以頭節(jié)點(diǎn)為基準(zhǔn)值。
  • 然后通過(guò)快慢指針 node_i、node_j 在鏈表中移動(dòng),使得 node_i 之前的節(jié)點(diǎn)值都小于基準(zhǔn)值,node_i 之后的節(jié)點(diǎn)值都大于基準(zhǔn)值。從而把數(shù)組拆分為左右兩個(gè)部分。
  • 再對(duì)左右兩個(gè)部分分別重復(fù)第二步,直到各個(gè)部分只有一個(gè)節(jié)點(diǎn),則排序結(jié)束。
def partition(self, left: ListNode, right: ListNode):
    # 左閉右開(kāi),區(qū)間沒(méi)有元素或者只有一個(gè)元素,直接返回第一個(gè)節(jié)點(diǎn)
    if left == right or left.next == right:
        return left
    # 選擇頭節(jié)點(diǎn)為基準(zhǔn)節(jié)點(diǎn)
    pivot = left.val
    # 使用 node_i, node_j 雙指針,保證 node_i 之前的節(jié)點(diǎn)值都小于基準(zhǔn)節(jié)點(diǎn)值,node_i 與 node_j 之間的節(jié)點(diǎn)值都大于等于基準(zhǔn)節(jié)點(diǎn)值
    node_i, node_j = left, left.next
    
    while node_j != right:
        # 當(dāng) node_j 的值小于基準(zhǔn)節(jié)點(diǎn)值時(shí),交換 node_i 與 node_j 的值
        if node_j.val < pivot:
            node_i = node_i.next
            node_i.val, node_j.val = node_j.val, node_i.val
        node_j = node_j.next
    # 將基準(zhǔn)節(jié)點(diǎn)放到正確位置上
    node_i.val, left.val = left.val, node_i.val
    return node_i
    
def quickSort(self, left: ListNode, right: ListNode):
    if left == right or left.next == right:
        return left
    pi = self.partition(left, right)
    self.quickSort(left, pi)
    self.quickSort(pi.next, right)
    return left

2.7 鏈表計(jì)數(shù)排序

算法描述:

  • 使用 cur 指針遍歷一遍鏈表。找出鏈表中最大值 list_max 和最小值 list_min。
  • 使用數(shù)組 counts 存儲(chǔ)節(jié)點(diǎn)出現(xiàn)次數(shù)。
  • 再次使用 cur 指針遍歷一遍鏈表。將鏈表中每個(gè)值為 cur.val 的節(jié)點(diǎn)出現(xiàn)次數(shù),存入數(shù)組對(duì)應(yīng)第 cur.val - list_min 項(xiàng)中。
  • 反向填充目標(biāo)鏈表:
    • 建立一個(gè)啞節(jié)點(diǎn) dummy_head,作為鏈表的頭節(jié)點(diǎn)。使用 cur 指針指向 dummy_head。
  • 從小到大遍歷一遍數(shù)組 counts。對(duì)于每個(gè) counts[i] != 0 的元素建立一個(gè)鏈節(jié)點(diǎn),值為 i + list_min,將其插入到 cur.next 上。并向右移動(dòng) cur。同時(shí) counts[i] -= 1。直到 counts[i] == 0 后繼續(xù)向后遍歷數(shù)組 counts。
  • 將啞節(jié)點(diǎn) dummy_dead 的下一個(gè)鏈節(jié)點(diǎn) dummy_head.next 作為新鏈表的頭節(jié)點(diǎn)返回。
def countingSort(self, head: ListNode):
    if not head:
        return head
    
    # 找出鏈表中最大值 list_max 和最小值 list_min
    list_min, list_max = float('inf'), float('-inf')
    cur = head
    while cur:
        if cur.val < list_min:
            list_min = cur.val
        if cur.val > list_max:
            list_max = cur.val
        cur = cur.next
        
    size = list_max - list_min + 1
    counts = [0 for _ in range(size)]
    
    cur = head
    while cur:
        counts[cur.val - list_min] += 1
        cur = cur.next
        
    dummy_head = ListNode(-1)
    cur = dummy_head
    for i in range(size):
        while counts[i]:
            cur.next = ListNode(i + list_min)
            counts[i] -= 1
            cur = cur.next
    return dummy_head.nextv

2.7 鏈表桶排序

算法描述:

  • 使用 cur 指針遍歷一遍鏈表。找出鏈表中最大值 list_max 和最小值 list_min。
  • 通過(guò) (最大值 - 最小值) / 每個(gè)桶的大小 計(jì)算出桶的個(gè)數(shù),即 bucket_count = (list_max - list_min) // bucket_size + 1 個(gè)桶。
  • 定義二維數(shù)組 buckets 為桶,外層數(shù)組大小為 bucket_count 個(gè)。
  • 使用 cur 指針再次遍歷一遍鏈表,將每個(gè)元素裝入對(duì)應(yīng)的桶中。
  • 對(duì)每個(gè)桶內(nèi)的元素單獨(dú)排序(使用插入、歸并、快排等算法)。
  • 最后按照順序?qū)⑼皟?nèi)的元素拼成新的鏈表,并返回。
def insertionSort(self, arr):
    for i in range(1, len(arr)):
        temp = arr[i]
        j = i
        while j > 0 and arr[j - 1] > temp:
            arr[j] = arr[j - 1]
            j -= 1
        arr[j] = temp
        
    return arr

def bucketSort(self, head: ListNode, bucket_size=5):
    if not head:
        return head
    
    # 找出鏈表中最大值 list_max 和最小值 list_min
    list_min, list_max = float('inf'), float('-inf')
    cur = head
    while cur:
        if cur.val < list_min:
            list_min = cur.val
        if cur.val > list_max:
            list_max = cur.val
        cur = cur.next
        
    # 計(jì)算桶的個(gè)數(shù),并定義桶
    bucket_count = (list_max - list_min) // bucket_size + 1
    buckets = [[] for _ in range(bucket_count)]
    
    # 將鏈表節(jié)點(diǎn)值依次添加到對(duì)應(yīng)桶中
    cur = head
    while cur:
        buckets[(cur.val - list_min) // bucket_size].append(cur.val)
        cur = cur.next
    
    dummy_head = ListNode(-1)
    cur = dummy_head
    for bucket in buckets:
        # 對(duì)桶中元素單獨(dú)排序
        self.sortLinkedList(bucket)
        for num in bucket:
            cur.next = ListNode(num)
            cur = cur.next
    
    return dummy_head.next

2.8 鏈表基數(shù)排序

算法描述:

  • 使用 cur 指針遍歷鏈表,獲取節(jié)點(diǎn)值位數(shù)最長(zhǎng)的位數(shù) size。
  • 從個(gè)位到高位遍歷位數(shù)。因?yàn)?0 ~ 9 共有 10 位數(shù)字,所以建立 10 個(gè)桶。
  • 以每個(gè)節(jié)點(diǎn)對(duì)應(yīng)位數(shù)上的數(shù)字為索引,將節(jié)點(diǎn)值放入到對(duì)應(yīng)桶中。
    建立一個(gè)啞節(jié)點(diǎn) dummy_head,作為鏈表的頭節(jié)點(diǎn)。使用 cur 指針指向 dummy_head。
  • 將桶中元素依次取出,并根據(jù)元素值建立鏈表節(jié)點(diǎn),并插入到新的鏈表后面。從而生成新的鏈表。
  • 之后依次以十位,百位,…,直到最大值元素的最高位處值為索引,放入到對(duì)應(yīng)桶中,并生成新的鏈表,最終完成排序。
  • 將啞節(jié)點(diǎn) dummy_dead 的下一個(gè)鏈節(jié)點(diǎn) dummy_head.next 作為新鏈表的頭節(jié)點(diǎn)返回。
def radixSort(self, head: ListNode):       
    # 計(jì)算位數(shù)最長(zhǎng)的位數(shù)
    size = 0
    cur = head
    while cur:
        val_len = len(str(cur.val))
        if val_len > size:
            size = val_len
        cur = cur.next
    
    # 從個(gè)位到高位遍歷位數(shù)
    for i in range(size):
        buckets = [[] for _ in range(10)]
        cur = head
        while cur:
            # 以每個(gè)節(jié)點(diǎn)對(duì)應(yīng)位數(shù)上的數(shù)字為索引,將節(jié)點(diǎn)值放入到對(duì)應(yīng)桶中
            buckets[cur.val // (10 ** i) % 10].append(cur.val)
            cur = cur.next
        
        # 生成新的鏈表
        dummy_head = ListNode(-1)
        cur = dummy_head
        for bucket in buckets:
            for num in bucket:
                cur.next = ListNode(num)
                cur = cur.next
        head = dummy_head.next
        
    return head

三、鏈表雙指針

雙指針(Two Pointers):指的是在遍歷元素的過(guò)程中,使用兩個(gè)指針進(jìn)行訪(fǎng)問(wèn),從而達(dá)到相應(yīng)的目的。如果兩個(gè)指針?lè)较蛳喾矗瑒t稱(chēng)為「對(duì)撞時(shí)針」。如果兩個(gè)指針?lè)较蛳嗤瑒t稱(chēng)為「快慢指針」。如果兩個(gè)指針?lè)謩e屬于不同的數(shù)組 / 鏈表,則稱(chēng)為「分離雙指針」。

3.1 起點(diǎn)不一致的快慢指針

起點(diǎn)不一致的快慢指針:指的是兩個(gè)指針從同一側(cè)開(kāi)始遍歷鏈表,但是兩個(gè)指針的起點(diǎn)不一樣。 快指針 fast 比慢指針 slow 先走 n 步,直到快指針移動(dòng)到鏈表尾端時(shí)為止。
求解步驟:

  • 使用兩個(gè)指針 slow、fast。slow、fast 都指向鏈表的頭節(jié)點(diǎn),即:slow = head,fast = head。
  • 先將快指針向右移動(dòng) n 步。然后再同時(shí)向右移動(dòng)快、慢指針。
  • 等到快指針移動(dòng)到鏈表尾部(即 fast == Node)時(shí)跳出循環(huán)體。
slow = head
fast = head

while n:
    fast = fast.next
    n -= 1
while fast:
    fast = fast.next
    slow = slow.next

3.2 步長(zhǎng)不一致的慢指針

步長(zhǎng)不一致的慢指針:指的是兩個(gè)指針從同一側(cè)開(kāi)始遍歷鏈表,兩個(gè)指針的起點(diǎn)一樣,但是步長(zhǎng)不一致。例如,慢指針 slow 每次走 1 步,快指針 fast 每次走兩步。直到快指針移動(dòng)到鏈表尾端時(shí)為止。
求解步驟:

  • 使用兩個(gè)指針 slow、fast。slow、fast 都指向鏈表的頭節(jié)點(diǎn)。
  • 在循環(huán)體中將快、慢指針同時(shí)向右移動(dòng),但是快、慢指針的移動(dòng)步長(zhǎng)不一致。比如將慢指針每次移動(dòng) 1 步,即 slow = slow.next。快指針每次移動(dòng) 2 步,即 fast = fast.next.next。
  • 等到快指針移動(dòng)到鏈表尾部(即 fast == Node)時(shí)跳出循環(huán)體。
fast = head
slow = head

while fast and fast.next:
    slow = slow.next
    fast = fast.next.next

3.3 分離雙指針

分離雙指針:兩個(gè)指針?lè)謩e屬于不同的鏈表,兩個(gè)指針?lè)謩e在兩個(gè)鏈表中移動(dòng)。
求解步驟:

  • 使用兩個(gè)指針 left_1、left_2。left_1 指向第一個(gè)鏈表頭節(jié)點(diǎn),即:left_1 = list1,left_2 指向第二個(gè)鏈表頭節(jié)點(diǎn),即:left_2 = list2。
  • 當(dāng)滿(mǎn)足一定條件時(shí),兩個(gè)指針同時(shí)右移,即 left_1 = left_1.next、left_2 = left_2.next。
  • 當(dāng)滿(mǎn)足另外一定條件時(shí),將 left_1 指針右移,即 left_1 = left_1.next。
  • 當(dāng)滿(mǎn)足其他一定條件時(shí),將 left_2 指針右移,即 left_2 = left_2.next。
  • 當(dāng)其中一個(gè)鏈表遍歷完時(shí)或者滿(mǎn)足其他特殊條件時(shí)跳出循環(huán)體。
left_1 = list1
left_2 = list2

while left_1 and left_2:
    if 一定條件 1:
        left_1 = left_1.next
        left_2 = left_2.next
    elif 一定條件 2:
        left_1 = left_1.next
    elif 一定條件 3:
        left_2 = left_2.next

四、總結(jié)

本章節(jié)主要學(xué)習(xí)了鏈表、排序和雙指針的基礎(chǔ)知識(shí),以及他們的求解方法,代碼實(shí)現(xiàn)等等。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,327評(píng)論 6 537
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,996評(píng)論 3 423
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 177,316評(píng)論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,406評(píng)論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,128評(píng)論 6 410
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,524評(píng)論 1 324
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,576評(píng)論 3 444
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 42,759評(píng)論 0 289
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,310評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,065評(píng)論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,249評(píng)論 1 371
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,821評(píng)論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,479評(píng)論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 34,909評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,140評(píng)論 1 290
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 51,984評(píng)論 3 395
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,228評(píng)論 2 375

推薦閱讀更多精彩內(nèi)容