參考鏈接: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ù)雜度為 ,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ù)雜度為 。
代碼如下:
# 獲取鏈表長(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ù)雜度為 。
# 查找元素
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ù)雜度為 。
# 頭部插入元素
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ù)雜度是。
# 尾部插入元素
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ù)雜度是,所以該算法的時(shí)間復(fù)雜度是
。
# 中間插入元素
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ù)雜度是 ,所以該算法的時(shí)間復(fù)雜度是
。
# 改變?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)等等。