当前位置: 首页 > news >正文

【Leetcode】

文章目录

    • 题目一:合并K个升序链表(LeetCode 23,困难)
      • 题目分析
      • 解题思路(优先队列+虚拟头节点)
      • 示例代码
      • 代码解析
    • 题目二:戳气球(LeetCode 312,困难)
      • 题目分析
      • 解题思路(区间动态规划+逆向思维)
      • 示例代码
      • 代码解析
    • 题目三:编辑距离(LeetCode 72,困难)
      • 题目分析
      • 解题思路(动态规划+状态细分)
      • 示例代码(空间优化版)
      • 代码解析

🌈你好呀!我是 山顶风景独好
🎈欢迎踏入我的博客世界,能与您在此邂逅,真是缘分使然!😊
🌸愿您在此停留的每一刻,都沐浴在轻松愉悦的氛围中。
📖这里不仅有丰富的知识和趣味横生的内容等您来探索,更是一个自由交流的平台,期待您留下独特的思考与见解。🌟
🚀让我们一起踏上这段探索与成长的旅程,携手挖掘更多可能,共同进步!💪✨

题目一:合并K个升序链表(LeetCode 23,困难)

题目分析

给你一个链表数组,每个链表都已经按升序排列。请你将所有链表合并到一个升序链表中,返回合并后的链表。例如:

  • 输入 lists = [[1,4,5],[1,3,4],[2,6]],输出 [1,1,2,3,4,4,5,6]
  • 输入 lists = [],输出 []
  • 输入 lists = [[]],输出 []
  • 输入 lists = [[-1,5,11],[6,10]],输出 [-1,5,6,10,11]

解题思路(优先队列+虚拟头节点)

核心是用最小堆(优先队列) 维护所有链表的当前头节点,每次取出最小节点接入结果链表,再将该节点的下一个节点加入堆,实现高效合并,具体步骤如下:

  1. 优先队列初始化

    • 因 Python 优先队列默认是最小堆,存储元素为 (节点值, 链表索引, 节点)——加入“链表索引”是为了避免节点值相同时直接比较节点对象报错;
    • 遍历链表数组,若链表非空,将其头节点加入优先队列。
  2. 构建结果链表

    • 创建虚拟头节点 dummy 和当前指针 curr:虚拟头节点用于简化“结果链表为空时接入第一个节点”的逻辑,curr 用于遍历结果链表;
    • 循环提取堆顶(最小节点):
      1. 将堆顶节点接入 currnext 指针;
      2. 若该节点的 next 非空,将 next 节点加入优先队列;
      3. 移动 curr 指针到下一个节点。
  3. 结果返回
    合并完成后,返回 dummy.next(虚拟头节点的下一个节点即为合并后链表的头)。

示例代码

import heapq# Definition for singly-linked list.
class ListNode:def __init__(self, val=0, next=None):self.val = valself.next = nextclass Solution:def mergeKLists(self, lists):heap = []# 初始化堆:将非空链表的头节点加入堆for idx, head in enumerate(lists):if head:heapq.heappush(heap, (head.val, idx, head))# 虚拟头节点简化拼接dummy = ListNode()curr = dummywhile heap:# 取出堆顶最小节点val, idx, node = heapq.heappop(heap)curr.next = nodecurr = curr.next# 若当前节点有下一个节点,加入堆if node.next:heapq.heappush(heap, (node.next.val, idx, node.next))return dummy.next# 辅助函数:数组转链表
def array_to_list(arr):dummy = ListNode()curr = dummyfor num in arr:curr.next = ListNode(num)curr = curr.nextreturn dummy.next# 辅助函数:链表转数组(用于测试输出)
def list_to_array(head):arr = []while head:arr.append(head.val)head = head.nextreturn arr# 测试示例
lists1 = [array_to_list([1,4,5]), array_to_list([1,3,4]), array_to_list([2,6])]
sol = Solution()
merged1 = sol.mergeKLists(lists1)
print("合并后链表1:", list_to_array(merged1))  # 输出:[1,1,2,3,4,4,5,6]lists2 = [array_to_list([-1,5,11]), array_to_list([6,10])]
merged2 = sol.mergeKLists(lists2)
print("合并后链表2:", list_to_array(merged2))  # 输出:[-1,5,6,10,11]

代码解析

  • 时间复杂度:每个节点入堆和出堆各一次,堆操作时间为 O(log k)k 为链表个数),总复杂度 O(N log k)N 为所有节点总数),远优于“两两合并”的 O(N k)
  • 空间复杂度:堆中最多存储 k 个节点,空间复杂度 O(k),虚拟头节点和指针仅用常数空间;
  • 关键细节:“链表索引”的加入解决了“节点值相同导致比较报错”的问题,确保堆操作正常执行。

题目二:戳气球(LeetCode 312,困难)

题目分析

n 个气球,编号为 0n-1,每个气球上标有数字 nums[i]。戳破第 i 个气球可获得 nums[i-1] * nums[i] * nums[i+1] 个硬币(i-1/i+1 超出边界时视为 1)。求戳破所有气球能获得的最大硬币数。例如:

  • 输入 nums = [3,1,5,8],输出 167(戳破顺序 1→5→3→8,总硬币 3×1×5 + 3×5×8 + 1×3×8 + 1×8×1 = 15+120+24+8=167);
  • 输入 nums = [1,5],输出 10(戳破顺序 1→5,总硬币 1×1×5 + 1×5×1=10);
  • 输入 nums = [2],输出 2(戳破后获得 1×2×1=2)。

解题思路(区间动态规划+逆向思维)

核心是逆向思维:将“戳破气球”转化为“添加气球”,用区间 DP 记录“区间内戳破所有气球的最大硬币数”,具体步骤如下:

  1. 问题转化与数组预处理

    • 逆向思维:若将“戳破气球 i”改为“在区间 [left, right] 中最后一个添加气球 i”,则此时 nums[left]nums[right]i 的左右邻居(区间内其他气球已添加),硬币数可通过子区间结果计算;
    • 预处理:在 nums 首尾各加 1(模拟边界虚拟气球),新数组长度为 n+2,原数组元素对应索引 1~n
  2. DP 状态定义
    定义 dp[left][right]:戳破区间 (left, right) 内所有气球(不包含 leftright)能获得的最大硬币数。

  3. 状态转移逻辑
    按区间长度 len = right - left 从小到大遍历(len ≥ 2,确保区间内至少有一个气球):

    • 枚举区间内最后一个添加的气球 midleft < mid < right);
    • 此时获得的硬币数 = 左区间硬币(dp[left][mid]) + 右区间硬币(dp[mid][right]) + 最后添加 mid 的硬币(nums[left] * nums[mid] * nums[right]);
    • 状态转移方程:dp[left][right] = max(dp[left][right], 左区间硬币 + 右区间硬币 + 新增硬币)
  4. 结果输出
    最终结果为 dp[0][n+1](戳破原数组所有气球,左右边界为虚拟气球 1)。

示例代码

def maxCoins(nums) -> int:n = len(nums)# 首尾加1,模拟边界虚拟气球nums = [1] + nums + [1]# dp[left][right]:戳破(left, right)内所有气球的最大硬币数dp = [[0] * (n + 2) for _ in range(n + 2)]# 按区间长度遍历(len至少为2,确保区间内有气球)for length in range(2, n + 2):for left in range(n + 2 - length):right = left + length# 枚举最后一个添加的气球midfor mid in range(left + 1, right):current = dp[left][mid] + dp[mid][right] + nums[left] * nums[mid] * nums[right]if current > dp[left][right]:dp[left][right] = currentreturn dp[0][n + 1]# 测试示例
print("最大硬币数1:", maxCoins([3,1,5,8]))  # 输出:167
print("最大硬币数2:", maxCoins([1,5]))  # 输出:10
print("最大硬币数3:", maxCoins([2]))  # 输出:2

代码解析

  • 逆向思维的价值:解决了“戳破气球后邻居变化导致状态难以追踪”的问题,将依赖未知状态的问题转化为依赖已知子区间的问题;
  • 时间复杂度:三重循环(区间长度、左边界、mid),复杂度 O(n³),在 n ≤ 500 约束下可高效运行;
  • 空间复杂度O(n²),用于存储 DP 表,无额外冗余空间。

题目三:编辑距离(LeetCode 72,困难)

题目分析

给你两个单词 word1word2,返回将 word1 转换成 word2 所需的最少操作数。允许的操作有:插入一个字符、删除一个字符、替换一个字符。例如:

  • 输入 word1 = "horse", word2 = "ros",输出 3horse → rorse → rose → ros);
  • 输入 word1 = "intention", word2 = "execution",输出 5
  • 输入 word1 = "", word2 = "",输出 0
  • 输入 word1 = "a", word2 = "",输出 1

解题思路(动态规划+状态细分)

核心是用 DP 记录“word1i 个字符转 word2j 个字符的最少操作数”,针对三种操作细分状态转移,具体步骤如下:

  1. DP 状态定义
    定义 dp[i][j]:将 word1[0..i-1] 转换成 word2[0..j-1] 所需的最少操作数。

  2. 边界初始化

    • dp[i][0] = i:将 word1i 个字符全部删除,得到空字符串 word2
    • dp[0][j] = j:在空字符串 word1 中插入 j 个字符,得到 word2j 个字符。
  3. 状态转移逻辑
    分两种情况讨论 word1[i-1]word2[j-1]

    • word1[i-1] == word2[j-1]:无需操作,dp[i][j] = dp[i-1][j-1]
    • word1[i-1] != word2[j-1]:取三种操作的最小值 + 1:
      • 替换:dp[i-1][j-1] + 1(替换 word1[i-1]word2[j-1]);
      • 删除:dp[i-1][j] + 1(删除 word1[i-1]);
      • 插入:dp[i][j-1] + 1(在 word1[i-1] 后插入 word2[j-1])。

示例代码(空间优化版)

def minDistance(word1: str, word2: str) -> int:m, n = len(word1), len(word2)# 优化为一维数组:dp[j] 表示当前行(word1前i个字符)转word2前j个字符的最少操作数dp = [0] * (n + 1)# 初始化第一行(word1为空,需插入j个字符)for j in range(n + 1):dp[j] = jfor i in range(1, m + 1):prev = dp[0]  # 保存dp[i-1][0]的值(删除i个字符)dp[0] = i     # 更新dp[i][0]for j in range(1, n + 1):temp = dp[j]  # 保存dp[i-1][j]的值if word1[i-1] == word2[j-1]:dp[j] = prev  # 无需操作,继承dp[i-1][j-1]else:# 取替换、删除、插入的最小值 + 1dp[j] = min(prev, dp[j], dp[j-1]) + 1prev = temp  # 更新prev为下一轮的dp[i-1][j-1]return dp[n]# 测试示例
print("编辑距离1:", minDistance("horse", "ros"))  # 输出:3
print("编辑距离2:", minDistance("intention", "execution"))  # 输出:5
print("编辑距离3:", minDistance("", ""))  # 输出:0
print("编辑距离4:", minDistance("a", ""))  # 输出:1

代码解析

  • 空间优化:利用“dp[i][j] 仅依赖 dp[i-1][j-1]dp[i-1][j]dp[i][j-1]”的特性,将二维数组优化为一维数组,空间复杂度从 O(mn) 降至 O(n)
  • 时间复杂度:双重循环遍历所有状态,复杂度 O(mn),是该问题的最优时间复杂度;
  • 实际应用:编辑距离是字符串相似度计算的核心算法,广泛用于拼写检查、DNA序列比对等场景。

✨ 本次分享的3道题均为LeetCode中“思维深度高、工程价值强”的困难题,覆盖“优先队列(合并链表)”“区间DP+逆向思维(戳气球)”“动态规划(编辑距离)”三大核心方向。它们的共同突破点在于:通过问题转化或数据结构选择,将复杂问题拆解为可分步求解的子问题,尤其适合提升“算法建模与优化”的能力。

若你对某题的拓展场景(如合并K个降序链表、多维度戳气球、带权重的编辑距离)或其他经典困难题有需求,随时告诉我!😊
🏠 更多算法解析欢迎到CSDN主页交流:山顶风景独好

http://www.dtcms.com/a/523835.html

相关文章:

  • 以LIS为突破口的全栈信创实践——浙江省人民医院多院区多活架构建设样本
  • 使用 IntelliJ IDEA 连接 Docker
  • Maya Python入门: polySphere()球体的形状节点操作
  • 目前最好的引流方法上海专业seo
  • 第一篇使用HTML写一个随机点名网页
  • 沈阳网站设计制作电子商务网站上线活动策划
  • 使用 Undertow 替代 Tomcat
  • 搜维尔科技将携手Xsens|Haption|Tesollo|Manus亮相IROS 2025国际智能机器人与系统会议
  • 第四章-Tomcat线程模型与运行方式
  • 【PB案例学习笔记】-46在数据窗口中编辑数据
  • tomcat问题
  • 爱电影网站个人养老金制度将落地
  • 自己做游戏网站电子商务营销是什么意思
  • 基于深度学习的短视频内容理解与推荐系统_hadoop+flask+spider
  • unbuntu系统配置IPV6的三种模式
  • ZVD振动抑制方法原理介绍
  • Java微服务无损发布生产案例
  • Kivy 乒乓游戏教程 基于Minconda或Anconda 运行
  • 摄影的网站设计特点同城发广告的平台有哪些
  • 【Python高级编程】类和实例化
  • 徐州市建设局交易网站网站设计的公司运营接单
  • 虹科亮相2025嵌入式会议 | 解读CAN XL与TSN如何驱动下一代E/E架构创新
  • VxWorks系统下龙芯平台的PCI驱动与硬件配置
  • 【2026计算机毕业设计】基于Django的新闻资讯平台的设计与实现
  • Linux小课堂: 基于 SSH 的安全文件传输与增量同步机制深度解析之从 wget 到 rsync 的全流程实战
  • 使用ffmpeg裁剪视频
  • 凡科建站网站西安全网推广公司
  • 免费网站建设程序下载建站用什么工具
  • 香港科技大学工学院2026/2027年度研究生课程招生宣讲会-重庆大学专场
  • Qualcomm SNPE(Neural Processing SDK)集成到 OpenWRT + QCS6490 的完整配置指南