链表基础
链表结构的定义
# 链节点类
class ListNode:
def __init__(self, val=0, next=None):
self.val = val
self.next = next
# 链表类
class LinkedList:
def __init__(self):
self.head = None
# 根据 data 初始化一个新链表 O(n)
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
# 获取链表长度 O(n)
def length(self):
count = 0
cur = self.head
while cur:
count += 1
cur = cur.next
return count
# 查找元素 O(n)
def find(self, val):
cur = self.head
while cur:
if val == cur.val:
return cur
cur = cur.next
return None
# 链表头部删除元素
def removeFront(self):
if self.head:
self.head = self.head.next
# 链表尾部删除元素
def removeRear(self):
if not self.head.next:
return 'Error'
cur = self.head
while cur.next.next:
cur = cur.next
cur.next = None
# 链表中间删除元素
def removeInside(self, index):
count = 0
cur = self.head
while cur.next and count < index - 1:
count += 1
cur = cur.next
if not cur:
return 'Error'
del_node = cur.next
cur.next = del_node.next
83.删除排序链表中的重复元素
-
题目
给定一个已排序的链表的头 head , 删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表
-
分析:
对链表进行循环,然后省略掉对每个结点中的重复结点, 即:将head赋值给cur 当cur.next存在时, 将cur 赋值给 temp 判断temp.next的值与temp的值是否相等, 如果相等,则跳过,即temp= temp.next 直至temp.next的值 != temp的值 将temp.next 赋值给cur 然后cur后移一位
-
代码
class Solution: class ListNode(): def __init__(self, val=0, next=None): self.val = val self.next = next def deleteDuplicates(self, head: ListNode) -> ListNode: cur = head while cur: temp = cur while temp.next and temp.next.val == temp.val: temp = temp.next cur.next = temp.next cur = cur.next return head
82.删除排序链表中的重复元素II
-
题目
给定一个已排序的链表的头 head , 删除原始链表中所有重复数字的节点,只留下不同的数字 。返回 已排序的链表
-
分析:
需要删除所有重复的元素, 即设置一个头结点,将所有符合的结点添加入头结点, 当指针cur的下个元素与下下个元素存在时: 如果这两个元素的值相同,则用temp保存cur的下一个元素,然后用temp进行遍历,跳过所有重复元素(判断条件为 temp and temp.next and temp.val == temp.next.val) 然后将temp.next(与前一个元素不同)赋值给cur.next,然后继续进行遍历 如果指针cur的下个元素与下下个元素不存在时 将cur右移一位,继续遍历
-
代码
class Solution: class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next def deleteDuplicates(self, head: ListNode) -> ListNode: head_li = self.ListNode(0, head) # 将头指针加入到head中,避免第一个元素重复 cur = head_li while cur.next and cur.next.next: if cur.next.val == cur.next.next.val: temp = cur.next while temp and temp.next and temp.next.val == temp.val: # 跳过重复元素 temp = temp.next cur.next = temp.next # 继续进行判断cur.next与cur.next.next是否相同,temp.next是这个循环的结点值的下一个 else: cur = cur.next return head_li.next
206.反转链表
-
题目
给你单链表的头节点 head ,请你反转链表,并返回反转后的链表
-
分析:
循环链表,对每个结点进行头插操作,防止对当前指针进行改动,则再进行改动之前需要将移动至下一位
-
代码
class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next class Solution: def reverseList(self, head: ListNode) -> ListNode: # 循环链表,对每个结点进行头插操作 head_res = ListNode(0) cur = head while cur: temp = cur cur = cur.next temp.next = head_res.next head_res.next = temp return head_res.next
92.反转链表II
-
题目
给你单链表的头指针 head 和两个整数 left 和 right ,其中 left <= right 。请你反转从位置 left 到位置 right 的链表节点,返回 反转后的链表
-
分析:
如果left与left相等,则没有进行反转直接返回, 先将cur移动到要反转链表的前一个结点,并保存到left_node 将cur(不包括)后right-left+1的结点进行反转存入一个新的链表reverse中 然后保存cur为right_node这时cur指向所有反转结点的后一个结点 然后将left_node与reverse拼接,然后遍历head,找到最后一个结点,拼接right_node 然后返回头结点
-
代码
# Definition for singly-linked list. class ListNode: def __init__(self, val=0, next=None): self.val = val self.next = next class Solution: def reverseBetween(self, head: ListNode, left: int, right: int) -> ListNode: if left == right: return head reverse = ListNode(0) head = ListNode(0, head) count = 0 # 进入到反转的链表的前一个结点 cur = head while cur and count < left - 1: count += 1 cur = cur.next # 这里cur为进行反转的链表的前一个结点 left_node = cur count = 0 cur = cur.next while cur and count < right - left + 1: temp = cur cur = cur.next temp.next = reverse.next reverse.next = temp count += 1 right_node = cur left_node.next = reverse.next cur = head while cur.next: cur = cur.next cur.next = right_node return head.next
链表排序
链表排序简介
-
适合链表排序的算法:冒泡排序、选择排序、插入排序、归并排序、快速排序、计数排序、桶排序、基数排序
-
不适合链表的排序算法:希尔排序
- 希尔排序中经常涉及到对序列中第
i + gap
的元素进行操作,其中gap
是希尔排序中当前的步长。而链表不支持随机访问的特性,导致这种操作不适合链表,因而希尔排序算法不适合进行链表排序
- 希尔排序中经常涉及到对序列中第
-
可以用链表排序的但不建议用的排序算法堆排序
-
堆排序所使用的最大堆 / 最小堆结构本质上是一棵完全二叉树。而完全二叉树适合采用顺序存储结构(数组)。因为数组存储的完全二叉树可以很方便的通过下标序号来确定父亲节点和孩子节点,并且可以极大限度的节省存储空间。
而链表用在存储完全二叉树的时候,因为不支持随机访问的特性,导致其寻找子节点和父亲节点会比较耗时,如果增加指向父亲节点的变量,又会浪费大量存储空间。所以堆排序算法不适合进行链表排序。
如果一定要对链表进行堆排序,则可以使用额外的数组空间表示堆结构。然后将链表中各个节点的值依次添加入堆结构中,对数组进行堆排序。排序后,再按照堆中元素顺序,依次建立链表节点,构建新的链表并返回新链表头节点
-
-
链表冒泡
-
使用三个指针,一个node_i、node_j和tail,node_i用于控制外层循环次数,node_j用于控制内层循环,tail指向链表尾部,即None,node_i,node_j指向头结点
-
比较链表中相邻两个元素,如果node_j.val>node_j.next.val,则值进行交换,否则值不进行交换,右移node_j指针,直到node_j==tail时停止
-
一次循环之后,将taile移动到node_j的位置,则node_j就是链表中值最大的结点
-
右移node_i,并将node_j置于头结点位置,重复2、3步骤
-
直到node_i移动到链表末尾停止,排序结束
-
返回头结点,head
代码:
def bubbleSort(head): node_i=head tail=None while node_i: node_j = head while node_j and node_j.next != tail: if node_j.val == node.next.val: node_j.val,node_j.next,val = node_j.next.val,node_j.val node_j = node_j.next taile = node_j node_i = node_i.next return head
-
-
链表选择排序
-
使用两个指针
node_i
、node_j
。node_i
既可以用于控制外循环次数,又可以作为当前未排序链表的第一个链节点位置 -
使用
min_node
记录当前未排序链表中值最小的链节点 -
每一趟排序开始时,先令
min_node = node_i
-
然后依次比较未排序链表中
node_j.val
与min_node.val
的值大小。如果node_j.val < min_node.val
,则更新min_node
为node_j
。 -
这一趟排序结束时,未排序链表中最小值节点为
min_node
,如果node_i != min_node
,则将node_i
与min_node
值进行交换。如果node_i == min_node
,则不用交换 -
排序结束后,继续向右移动
node_i
,重复上述步骤,在剩余未排序链表中寻找最小的链节点,并与node_i
进行比较和交换,直到node_i == None
或者node_i.next == None
时,停止排序 -
返回链表的头节点
head
代码:
def selectSort(head): node_i = head while node_i: min_node = node_i node_j = node_i while node_j: if node_j.val <min_node.val: min_node.val = node_j.val node_j = node_j,next if node_i.val !=min_node.val: node_i.val,min_node.val = min_node.val,node_i.val node_i= node_i.next return head
-
-
链表插入排序
-
先使用哑节点
dummy_head
构造一个指向head
的指针,使得可以从head
开始遍历。 -
维护
sorted_list
为链表的已排序部分的最后一个节点,初始时,sorted_list = head
。 -
维护
prev
为插入元素位置的前一个节点,维护cur
为待插入元素。初始时,prev = head
,cur = head.next
。 -
比较
sorted_list
和cur
的节点值。- 如果
sorted_list.val <= cur.val
,说明cur
应该插入到sorted_list
之后,则将sorted_list
后移一位。 - 如果
sorted_list.val > cur.val
,说明cur
应该插入到head
与sorted_list
之间。则使用prev
从head
开始遍历,直到找到插入cur
的位置的前一个节点位置。然后将cur
插入。
- 如果
-
令
cur = sorted_list.next
,此时cur
为下一个待插入元素。 -
重复 4、5 步骤,直到
cur
遍历结束为空。返回dummy_head
的下一个节点。代码
def insertSort(head): if not head and not head.next: return head dummy_head=ListNode(-1,head) sort_head = head cur = head.next sorted_list = head while cur: if sorted_list.val <=cur.val: sorted_list = sorted_list.next else: prev = dummy_head while prev.next.val <=cur.head: prev = prev.next sort_list.next = cur.next cur.next = prev.next prev.next = cur cur = sorted_list.next return dummy_head.next
-
-
链表归并排序
-
分割环节:找到链表中心链节点,从中心节点将链表断开,并递归进行分割。
- 使用快慢指针
fast = head.next
、slow = head
,让fast
每次移动2
步,slow
移动1
步,移动到链表末尾,从而找到链表中心链节点,即slow
。 - 从中心位置将链表从中心位置分为左右两个链表
left_head
和right_head
,并从中心位置将其断开,即slow.next = None
。 - 对左右两个链表分别进行递归分割,直到每个链表中只包含一个链节点。
- 使用快慢指针
-
归并环节:将递归后的链表进行两两归并,完成一遍后每个子链表长度加倍。重复进行归并操作,直到得到完整的链表。
- 使用哑节点
dummy_head
构造一个头节点,并使用cur
指向dummy_head
用于遍历。 - 比较两个链表头节点
left
和right
的值大小。将较小的头节点加入到合并后的链表中。并向后移动该链表的头节点指针。 - 然后重复上一步操作,直到两个链表中出现链表为空的情况。
- 将剩余链表插入到合并中的链表中。
- 将哑节点
dummy_dead
的下一个链节点dummy_head.next
作为合并后的头节点返回
代码
def merge(left,right): 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(head): if not head and not head.next: return head slow,fast = head,head.next while fast and fast.next:# 在中间进行切割 slow = slow.next fast = fast.next.next left_head,right_head = head,slow.next slow.next = None # 断开左右链表 return merge(mergesort(left_head),merge(right_head)
- 使用哑节点
-
-
链表快速排序
-
从链表中找到一个基准值
pivot
,这里以头节点为基准值。 -
然后通过快慢指针
node_i
、node_j
在链表中移动,使得node_i
之前的节点值都小于基准值,node_i
之后的节点值都大于基准值。从而把数组拆分为左右两个部分。 -
再对左右两个部分分别重复第二步,直到各个部分只有一个节点,则排序结束。
代码:
def partition(left,right): # 区间左闭右开,如果只有一个元素则直接返回第一个结点 if left==right or left.next ==right: return left # 选择基准结点 pivot = left.val node_i,node_j = left,left.next while node_j !=right: 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 node_i.val,left.val = left.val,node_i.val return node_i def quick_sort(left,right): if left==right or left.next ==right: return left pi = partition(left,right) quicksort(left,pi) quicksort(pi.next,right) return left
-
-
链表计数排序
-
使用
cur
指针遍历一遍链表。找出链表中最大值list_max
和最小值list_min
。 -
使用数组
counts
存储节点出现次数。 -
再次使用
cur
指针遍历一遍链表。将链表中每个值为cur.val
的节点出现次数,存入数组对应第cur.val - list_min
项中。 -
反向填充目标链表:
- 建立一个哑节点
dummy_head
,作为链表的头节点。使用cur
指针指向dummy_head
。 - 从小到大遍历一遍数组
counts
。对于每个counts[i] != 0
的元素建立一个链节点,值为i + list_min
,将其插入到cur.next
上。并向右移动cur
。同时counts[i] -= 1
。直到counts[i] == 0
后继续向后遍历数组counts
。
- 建立一个哑节点
-
将哑节点
dummy_dead
的下一个链节点dummy_head.next
作为新链表的头节点返回。代码
def countSort(head): if not head: return head # 找出最大值与最小值 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]*size cur = head while cur: count[cur.val-list_min] +=1 cur = cur.next dummy_head = ListNode(-1) cur = dummy_head for i in range(size): while count[i]: cur.next = ListNode(i+list_min) cur = cur.next count[i] -=1 return dummy_head.next
-
-
链表桶排序
-
使用
cur
指针遍历一遍链表。找出链表中最大值list_max
和最小值list_min
。 -
通过
(最大值 - 最小值) / 每个桶的大小
计算出桶的个数,即bucket_count = (list_max - list_min) // bucket_size + 1
个桶。 -
定义二维数组
buckets
为桶,外层数组大小为bucket_count
个。 -
使用
cur
指针再次遍历一遍链表,将每个元素装入对应的桶中。 -
对每个桶内的元素单独排序(使用插入、归并、快排等算法)。
-
最后按照顺序将桶内的元素拼成新的链表,并返回。
代码
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 # 计算桶的个数,并定义桶 bucket_count = (list_max - list_min) // bucket_size + 1 buckets = [[] for _ in range(bucket_count)] # 将链表节点值依次添加到对应桶中 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: # 对桶中元素单独排序 self.sortLinkedList(bucket) for num in bucket: cur.next = ListNode(num) cur = cur.next return dummy_head.next
-
-
链表基数排序
-
使用
cur
指针遍历链表,获取节点值位数最长的位数size
。 -
从个位到高位遍历位数。因为
0
~9
共有10
位数字,所以建立10
个桶。 -
以每个节点对应位数上的数字为索引,将节点值放入到对应桶中。
-
建立一个哑节点
dummy_head
,作为链表的头节点。使用cur
指针指向dummy_head
。 -
将桶中元素依次取出,并根据元素值建立链表节点,并插入到新的链表后面。从而生成新的链表。
-
之后依次以十位,百位,…,直到最大值元素的最高位处值为索引,放入到对应桶中,并生成新的链表,最终完成排序。
-
将哑节点
dummy_dead
的下一个链节点dummy_head.next
作为新链表的头节点返回。代码
def radixSort(self, head: ListNode): # 计算位数最长的位数 size = 0 cur = head while cur: val_len = len(str(cur.val)) if val_len > size: size = val_len cur = cur.next # 从个位到高位遍历位数 for i in range(size): buckets = [[] for _ in range(10)] cur = head while cur: # 以每个节点对应位数上的数字为索引,将节点值放入到对应桶中 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
-
148.排序链表
-
题目
-
给你链表的头结点 head ,请将其按 升序 排列并返回 排序后的链表
-
-
分析:
-
可采用链表排序中任何一种算法
-
-
代码
-
class Solution: def sortList(self, head: Optional[ListNode]) -> Optional[ListNode]: return self.quicksort(head,None) def quicksort(self,left,right): if left == right or left.next == right: return left pi = self.potition(left,right) self.quicksort(left,pi) self.quicksort(pi.next,right) return left def potition(self,left,right): if left==right or left.next == right: return left pivot = left.val node_i,node_j = left,left.next while node_j !=right: 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 left.val,node_i.val = node_i.val,left.val return node_i
-
21.合并两个有序链表
-
题目
-
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
-
-
分析
-
直接采用双指针,比较两个链表表中的值,然后在新链表中拼接
-
-
代码
-
class Solution: def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: cur1 = list1 cur2 = list2 head = ListNode() cur = head while cur1 is not None and cur2 is not None: if cur1.val <= cur2.val: cur.next =cur1 cur = cur.next cur1 = cur1.next else: cur.next = cur2 cur = cur.next cur2 = cur2.next if cur1 is None: cur.next = cur2 else: cur.next = cur1 return head.next
-
24.合并K个升序链表
-
题目
-
给你一个链表数组,每个链表都已经按升序排列。 请你将所有链表合并到一个升序链表中,返回合并后的链表
-
-
分析
-
使用优先级队列,将每一个链表的头结点存入优先级队列中,然后选取队列中最小的值,然后在那拿出结点的链表,该链表后移一位,然后再存入优先级队列,直到链表为空
-
-
class Solution: def mergeKLists(self, lists: List[Optional[ListNode]]) -> Optional[ListNode]: from queue import PriorityQueue pq = PriorityQueue() head = ListNode(0) cur = head for i in range(len(lists)): if lists[i] is not None: pq.put((lists[i].val,i)) while not pq.empty(): idx = pq.get()[1] li = lists[idx] cur.next = li cur = cur.next lists[idx] = lists[idx].next if lists[idx] is not None: pq.put((lists[idx].val,idx)) return head.next
链表双指针
起点不一致的快慢指针
起点不一致的快慢指针:指的是两个指针从同一侧开始遍历链表,但是两个指针的起点不一样。 快指针
fast
比慢指针slow
先走n
步,直到快指针移动到链表尾端时为止
-
求解步骤
- 使用两个指针
slow
、fast
。slow
、fast
都指向链表的头节点,即:slow = head
,fast = head
。 - 先将快指针向右移动
n
步。然后再同时向右移动快、慢指针。 - 等到快指针移动到链表尾部(即
fast == None
)时跳出循环体
- 使用两个指针
-
代码模板
-
slow = head fast = head while n: fast = fast.next n -= 1 while fast: fast = fast.next slow = slow.next
-
-
适用范围:
- 起点不一致的快慢指针主要用于找到链表中倒数第 k 个节点、删除链表倒数第 N 个节点等
19.删除链表的倒数第N个结点
-
题目:
-
给你一个链表,删除链表的倒数第 n 个结点,并且返回链表的头结点
-
-
分析:
-
使用起点不一样的快慢指针,先将快指针走n步,然后快慢指针一起移动,当快指针达到末尾是,慢指针刚好指向倒数第n个结点的前一个结点
-
-
代码:
-
class Solution: def removeNthFromEnd(self, head: ListNode, n: int) -> ListNode: head = ListNode(0,head) fast,slow = head,head while n: fast=fast.next n-=1 while fast.next: fast = fast.next slow = slow.next slow.next = slow.next.next return head.next
-
步长不一样的快慢指针
步长不一致的慢指针:指的是两个指针从同一侧开始遍历链表,两个指针的起点一样,但是步长不一致。例如,慢指针
slow
每次走1
步,快指针fast
每次走两步。直到快指针移动到链表尾端时为止
-
求解步骤
- 使用两个指针
slow
、fast
。slow
、fast
都指向链表的头节点。 - 在循环体中将快、慢指针同时向右移动,但是快、慢指针的移动步长不一致。比如将慢指针每次移动
1
步,即slow = slow.next
。快指针每次移动2
步,即fast = fast.next.next
。 - 等到快指针移动到链表尾部(即
fast == None
)时跳出循环体
- 使用两个指针
-
代码模板
-
fast = head slow = head while fast and fast.next: slow = slow.next fast = fast.next.next
-
-
使用范围
- 步长不一致的快慢指针适合寻找链表的中点、判断和检测链表是否有环、找到两个链表的交点等问题。
876.链表的中间结点
-
题目
-
给定一个头结点为 head 的非空单链表,返回链表的中间结点。 如果有两个中间结点,则返回第二个中间结点
-
-
分析
-
slow移动一步,fast移动两步,当fast与fast.next不存在时,slow即中间结点
-
-
代码:
-
class Solution: def middleNode(self, head: ListNode) -> ListNode: if not head: return head slow = head fast = head while fast and fast.next: slow = slow.next fast = fast.next.next return slow
-
分离双指针
分离双指针:两个指针分别属于不同的链表,两个指针分别在两个链表中移动
-
求解步骤
- 使用两个指针
left_1
、left_2
。left_1
指向第一个链表头节点,即:left_1 = list1
,left_2
指向第二个链表头节点,即:left_2 = list2
。 - 当满足一定条件时,两个指针同时右移,即
left_1 = left_1.next
、left_2 = left_2.next
。 - 当满足另外一定条件时,将
left_1
指针右移,即left_1 = left_1.next
。 - 当满足其他一定条件时,将
left_2
指针右移,即left_2 = left_2.next
。 - 当其中一个链表遍历完时或者满足其他特殊条件时跳出循环体
- 使用两个指针
-
代码模板
-
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
-
21.合并两个有序链表
-
题目
-
将两个升序链表合并为一个新的 升序 链表并返回。新链表是通过拼接给定的两个链表的所有节点组成的。
-
-
分析
-
直接采用双指针,比较两个链表表中的值,然后在新链表中拼接
-
-
代码
-
class Solution: def mergeTwoLists(self, list1: Optional[ListNode], list2: Optional[ListNode]) -> Optional[ListNode]: cur1 = list1 cur2 = list2 head = ListNode() cur = head while cur1 is not None and cur2 is not None: if cur1.val <= cur2.val: cur.next =cur1 cur = cur.next cur1 = cur1.next else: cur.next = cur2 cur = cur.next cur2 = cur2.next if cur1 is None: cur.next = cur2 else: cur.next = cur1 return head.next
-