Leetcode 链表 4
题号直达: 148 排序链表 | 23 合并 K 个升序链表 | 146 LRU 缓存
LeetCode 148. 排序链表 ↑
题目
给你链表的头结点 head,请将其按升序排列并返回排序后的链表。
提示
- 链表中节点数目在范围
[0, 5 x 10^4]内 -10^5 ≤ Node.val ≤ 10^5
进阶:你能否在 O(n log n) 时间复杂度和 常数级空间复杂度下解决此题?
代码(Python)解法一:取值排序后重建链表
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def sortList(self, head):
"""
:type head: Optional[ListNode]
:rtype: Optional[ListNode]
"""
if not head:
return None
p = head
record = []
while p != None:
record.append(p.val)
p = p.next
record.sort()
ans = ListNode(record[0])
newHead = ans
for i in range(1, len(record)):
ans.next = ListNode(record[i])
ans = ans.next
ans.next = None
return newHead
思路解析(解法一)
- 整体思路:遍历链表,把所有节点的值放进列表
record,对列表排序后,再按顺序新建一条链表。 - 空链表:
head为None时直接返回None。 - 重建链表:用第一个值建头结点
newHead,后续for循环依次ListNode(record[i])接到尾部,最后将尾结点next置为None。 - 复杂度:时间 O(n log n)(Python
list.sort为 Timsort);空间 O(n)(列表 + 新链表节点),不满足进阶的 O(1) 额外空间;若需常数额外空间,需在链表上做归并排序等指针操作。
代码(Python)解法二:自顶向下归并排序
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def sortList(self, head):
return self._sortList(head, None)
def _sortList(self, head, tail):
if head is None:
return head
if head.next == tail:
head.next = None
return head
# 使用快慢指针找中点
slow = head
fast = head
while fast != tail:
slow = slow.next
fast = fast.next
if fast != tail:
fast = fast.next
mid = slow
return self._merge(self._sortList(head, mid), self._sortList(mid, tail))
def _merge(self, head1, head2):
dummyHead = ListNode(0)
temp = dummyHead
temp1 = head1
temp2 = head2
while temp1 is not None and temp2 is not None:
if temp1.val <= temp2.val:
temp.next = temp1
temp1 = temp1.next
else:
temp.next = temp2
temp2 = temp2.next
temp = temp.next
if temp1 is not None:
temp.next = temp1
elif temp2 is not None:
temp.next = temp2
return dummyHead.next
思路解析(解法二)
- 核心:自顶向下的链表归并排序。用区间
[head, tail)表示待排序段(tail为右开边界,整链调用为_sortList(head, None))。 - 递归终止:
head is None直接返回;仅有一个结点(head.next == tail)时断开head.next = None,返回该结点作为已排序单元素链。 - 找中点:快慢指针从
head出发,fast每次两步、slow一步,且fast未到tail时继续;slow最终指向左半段最后一个结点,中点即mid = slow。左段为[head, mid),右段为[mid, tail),递归排序后_merge合并。 - 合并:哑结点 + 双指针遍历两条有序链,取较小者接在
temp后;一段耗尽后把另一段剩余整体接上。 - 复杂度:时间 O(n log n)(每层合并线性,共 O(log n) 层);额外空间主要为递归栈 O(log n)。若题目要求严格 O(1) 额外空间,需改用自底向上的迭代归并(按子链长度 1、2、4… 倍增合并)。
代码(Python)解法三:自底向上归并排序
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution:
def sortList(self, head):
# Necessity
if head == None:
return head
# 先获得链表长度Length
length = 0
node = head
while node != None:
length = length + 1
node = node.next
dummyHead = ListNode(0)
dummyHead.next = head
subLength = 1
while subLength < length:
prev = dummyHead
curr = dummyHead.next
# 以subLength长度的单位链表的一轮合并
while curr != None:
head1 = curr
# 使curr到达现在的curr后subLength-1位置的地方
for i in range(1, subLength):
if curr.next == None:
break
curr = curr.next
# 这里把curr和curr后面的那个节点断开了
head2 = curr.next
curr.next = None
curr = head2
# 再往后移动subLength-1个单位
for i in range(1, subLength):
if curr == None or curr.next == None:
break
curr = curr.next
# nex是curr的下一个节点,这里再一次将curr和curr.next断开
nex = None
if curr != None:
nex = curr.next
curr.next = None # 保证要合并的链表尾部指向None
# 合并链表
merged = self.merge(head1, head2)
prev.next = merged
while prev.next != None:
prev = prev.next
# 再从下一个地方合并
curr = nex
subLength *= 2
return dummyHead.next
def merge(self, head1, head2):
dummyHead = ListNode(0)
temp = dummyHead
temp1 = head1
temp2 = head2
while temp1 != None and temp2 != None:
if temp1.val <= temp2.val:
temp.next = temp1
temp1 = temp1.next
else:
temp.next = temp2
temp2 = temp2.next
temp = temp.next
# 连接剩余部分 - 使用两个独立的 if
if temp1 != None:
temp.next = temp1
if temp2 != None:
temp.next = temp2
return dummyHead.next
思路解析(解法三)
- 核心:自底向上归并,无递归。子链长度
subLength从 1 开始,每轮结束后乘 2(1 → 2 → 4 → …),直到subLength >= length。每轮在整条链上依次取出两段长度为subLength的有序子链(不足则只取一段或更短),断开后merge再接回prev之后。 - 哑结点:
dummyHead方便在头前维护prev,避免对头结点特殊处理。 - 一轮内
curr扫描:head1为当前段起点;走subLength-1步得到第一段尾,再head2 = curr.next并断开;第二段再走至多subLength-1步,用nex记下下一轮起点,并把第二段尾与后续断开,得到两条待合并子链。 - 接回:
merged = self.merge(head1, head2),prev.next = merged,再把prev移到合并结果的尾,继续处理curr = nex。 - 复杂度:时间 O(n log n);仅若干指针与哑结点,额外空间 O(1),满足进阶要求。
LeetCode 23. 合并 K 个升序链表 ↑
题目
给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。
示例(摘要)
- 示例 1:
lists = [[1,4,5],[1,3,4],[2,6]]→[1,1,2,3,4,4,5,6] - 示例 2:
lists = []→[] - 示例 3:
lists = [[]]→[]
提示
k == lists.length0 ≤ k ≤ 10^40 ≤ lists[i].length ≤ 500-10^4 ≤ lists[i][j] ≤ 10^4lists[i]按升序排列- 所有链表中的节点数目总数不超过
10^4
代码(Python)解法一:顺序两两合并
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def mergeKLists(self, lists):
"""
:type lists: List[Optional[ListNode]]
:rtype: Optional[ListNode]
"""
if not lists:
return None
N = len(lists)
head = ListNode(0)
head.next = lists[0]
for i in range(1, N):
head.next = self.mergeTwoLists(head.next, lists[i])
return head.next
def mergeTwoLists(self, list1, list2):
"""
:type list1: Optional[ListNode]
:type list2: Optional[ListNode]
:rtype: Optional[ListNode]
"""
if not list1:
return list2
if not list2:
return list1
p1 = list1
p2 = list2
if list1.val < list2.val:
p = list1
p1 = p1.next
else:
p = list2
p2 = p2.next
while p1 != None and p2 != None:
if p1.val < p2.val:
p.next = p1
p = p.next
p1 = p1.next
else:
p.next = p2
p = p.next
p2 = p2.next
if not p1:
while p2 != None:
p.next = p2
p = p.next
p2 = p2.next
else:
while p1 != None:
p.next = p1
p = p.next
p1 = p1.next
if list1.val < list2.val:
return list1
else:
return list2
思路解析(解法一)
- 主流程:用哑结点
head保存当前「已合并结果」的头指针(head.next)。从第二条链开始,依次执行head.next = mergeTwoLists(head.next, lists[i]),相当于把前i条链的合并结果再与第i+1条链归并。 - mergeTwoLists:与「合并两个有序链表」相同,双指针比较
val,较小者接到p后;一段耗尽后把另一段剩余整体接上;最后根据最初两个头结点的大小关系返回真正的结果头(与链表 3 笔记中 21 题写法一致)。 - 边界:
lists为空返回None;仅一条链时循环不执行,直接返回lists[0]。 - 复杂度:设总节点数为
n。第i次合并代价约与当前结果长度成正比,最坏约O(nk)(k为链表条数);空间 O(1)(不计输出链表)。更优可做分治归并或优先队列,达到约 O(n log k) 时间。
代码(Python)解法二:分治式迭代归并(O(n log k))

# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
class Solution(object):
def mergeKLists(self, lists):
"""
:type lists: List[Optional[ListNode]]
:rtype: Optional[ListNode]
"""
if not lists:
return None
N = len(lists)
step = 1
while step < N:
start = 0
index = 0
while index < len(lists):
index = start + step
lists[start] = self.mergeTwoLists(lists[start], lists[index])
start = start + 2 * step
index = index + 2 * step
step = step * 2
return lists[0]
def mergeTwoLists(self, list1, list2):
"""
:type list1: Optional[ListNode]
:type list2: Optional[ListNode]
:rtype: Optional[ListNode]
"""
if not list1:
return list2
if not list2:
return list1
p1 = list1
p2 = list2
if list1.val < list2.val:
p = list1
p1 = p1.next
else:
p = list2
p2 = p2.next
while p1 != None and p2 != None:
if p1.val < p2.val:
p.next = p1
p = p.next
p1 = p1.next
else:
p.next = p2
p = p.next
p2 = p2.next
if not p1:
while p2 != None:
p.next = p2
p = p.next
p2 = p2.next
else:
while p1 != None:
p.next = p1
p = p.next
p1 = p1.next
if list1.val < list2.val:
return list1
else:
return list2
思路解析(解法二)
- 核心:与归并排序同构——每轮令步长
step为 1、2、4…,在数组lists上把间隔为step的两条有序链合并到左侧槽位lists[start],下一轮链长翻倍,约 log k 轮合并完。 - 内层循环:
start每次跳2*step,对应一对对(start, start+step);index用于控制是否还有下一段可配对;合并结果写回lists[start],与「分治」中子问题合并回父区间一致。 - 与解法一对比:解法一总是把「当前大结果」与「下一条链」合并,链长极不均衡,时间偏 O(nk);本解法每轮合并的段长更接近,总合并量约 O(n log k)。
- 空间:O(1) 额外指针(不计输出与
lists数组本身),mergeTwoLists同 21 题原地归并。
代码(Python)解法三:小根堆(优先队列)
# Definition for singly-linked list.
# class ListNode(object):
# def __init__(self, val=0, next=None):
# self.val = val
# self.next = next
import heapq
class Solution(object):
def mergeKLists(self, lists):
"""
:type lists: List[Optional[ListNode]]
:rtype: Optional[ListNode]
"""
# 使用堆来实现优先队列
# 堆中存储 (节点值, 索引, 节点) 为了避免比较节点对象
heap = []
# 将所有非空链表的头节点加入堆
for i, node in enumerate(lists):
if node:
heapq.heappush(heap, (node.val, i, node))
# 虚拟头节点
dummy = ListNode(0)
tail = dummy
# 当堆不为空时循环
while heap:
# 弹出值最小的节点
val, i, node = heapq.heappop(heap)
# 将节点接到结果链表
tail.next = node
tail = tail.next
# 如果该节点还有下一个节点,将其加入堆
if node.next:
heapq.heappush(heap, (node.next.val, i, node.next))
return dummy.next
思路解析(解法三)
- 核心:堆可以保证按从小到大的顺序弹出。可以维护一个小根堆,始终保存每条链表当前的第一个未合并结点;每次弹出全局最小
val的结点接到结果尾,再把该结点所在链的next入堆,等价于 k 路归并。 - 元组
(val, i, node):堆比较元组时先比val,相等再比链表下标i,避免在 Python 3 中直接比较ListNode对象(可能未定义序或不符合预期)。 - 复杂度:共 n 次入堆/出堆,每次 O(log k),时间 O(n log k);堆内至多 k 个元素,额外空间 O(k)。
LeetCode 146. LRU 缓存 ↑
题目
请你设计并实现一个满足 LRU(最近最少使用) 缓存约束的数据结构。
实现 LRUCache 类:
LRUCache(int capacity)以正整数作为容量capacity初始化 LRU 缓存int get(int key)如果关键字key存在于缓存中,则返回关键字的值,否则返回-1void put(int key, int value)如果关键字key已经存在,则变更其数据值value;如果不存在,则向缓存中插入该组key-value。当缓存容量达到上限时,它应该在写入新数据之前删除最久未使用的数据值,从而为新的数据值留出空间
进阶:你是否可以在 O(1) 时间复杂度内完成这两种操作?
示例(摘要)
capacity = 2,依次 put(1,1)、put(2,2)、get(1) → 1;put(3,3) 淘汰 key 2;get(2) → -1;put(4,4) 淘汰 key 1;get(1) → -1,get(3) → 3,get(4) → 4。
提示
1 ≤ capacity ≤ 30000 ≤ key ≤ 10^40 ≤ value ≤ 10^5- 最多调用
2 x 10^5次get和put
代码(Python):哈希表 + 双向链表
# 构建双链表
class DLinkedNode:
def __init__(self, key=0, value=0):
self.key = key
self.value = value
self.prev = None
self.next = None
class LRUCache():
def __init__(self, capacity):
"""
:type capacity: int
"""
# 创建哈希表
self.cache = dict()
# 使用伪头部和伪尾部节点
self.head = DLinkedNode()
self.tail = DLinkedNode()
self.head.next = self.tail
self.tail.prev = self.head
self.capacity = capacity
self.size = 0
def get(self, key):
"""
:type key: int
:rtype: int
"""
if key not in self.cache:
return -1
# 刚被提及,放到最后一个删的位置
node = self.cache[key]
self.moveToHead(node)
return node.value
def put(self, key, value):
"""
:type key: int
:type value: int
:rtype: None
"""
if key not in self.cache:
# 如果key不在,创建一个新节点
node = DLinkedNode(key, value)
# 添加至哈希表
self.cache[key] = node
# 添加至双向链表的头部
self.addToHead(node)
self.size += 1
if self.size > self.capacity:
# 超出容量,删除双向链表的尾节点
removed = self.removeTail()
# 删除哈希表中的对应项
self.cache.pop(removed.key)
self.size -= 1
else:
# key存在,先通过哈希表定位,再修改value,并移到头部
node = self.cache[key]
node.value = value
self.moveToHead(node)
def addToHead(self, node):
node.prev = self.head
node.next = self.head.next
self.head.next.prev = node
self.head.next = node
def removeNode(self, node):
# Node 指向别人无所谓,但是其他Node已经没有指向它的了
node.prev.next = node.next
node.next.prev = node.prev
def moveToHead(self, node):
self.removeNode(node)
self.addToHead(node)
def removeTail(self):
node = self.tail.prev
self.removeNode(node)
# 因为还要将哈希表中的对应字段删掉,所以要return一下
return node
思路解析
- 为什么需要两种结构:
dict按 key O(1) 找到结点;仅靠 dict 无法 O(1) 知道「谁最久未用」。双向链表按使用顺序串起结点:靠近头表示最近使用,靠近尾表示最久未用;删除/移动前后指针均为 O(1)。 - 哑头 / 哑尾:
head与tail不存业务数据,避免对头尾结点的特殊判断。 get:未命中返回 -1;命中则moveToHead(先removeNode再addToHead),表示刚被访问。put:新 key 则新建结点、入表、挂到头部,size超容量则removeTail(真尾的前驱即 LRU),并从cache删除;已存在 key 则改value并moveToHead。- 复杂度:单次
get/put均为 O(1);额外空间 O(capacity)。

浙公网安备 33010602011771号