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.length
  • 0 ≤ k ≤ 10^4
  • 0 ≤ lists[i].length ≤ 500
  • -10^4 ≤ lists[i][j] ≤ 10^4
  • lists[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))

合并 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 存在于缓存中,则返回关键字的值,否则返回 -1
  • void 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 ≤ 3000
  • 0 ≤ key ≤ 10^4
  • 0 ≤ 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)。

posted @ 2026-10-11 23:37  Phier  阅读(2)  评论(0)    收藏  举报