Doocs LeetCode 题解深度解析:LCP 59「搭桥过河」——区间重叠约束下最小移动代价的动态规划
示例工程【免费下载链接】leetcodeLeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版》、《程序员面试金典第 6 版》题解项目地址https://gitcode.com/doocs/leetcode点击查看免费下载本篇围绕开源仓库 doocs/leetcode 中收录的力扣 LCP 59「搭桥过河」题目文档lcp/LCP 59. 搭桥过河/README.md展开完整讲解题目规则、三个官方示例的推演过程并推导出一套基于「区间重叠判定 动态规划」的可行解法与复杂度分析。读完本文你将掌握如何把移动一维线段使相邻线段两两重叠这类区间调度问题抽象成序列 DP并能独立实现可复现的求解代码。题目背景与核心规则欢迎各位勇者来到力扣城本次试炼主题为「搭桥过河」。题目描述如下勇者面前有一段长度为num的河流河流可以划分为若干河道。每条河道上恰有一块浮木wood[i]记录了第i条河道上的浮木初始的覆盖范围wood[i] [left, right]即该浮木覆盖从left到right的区间。移动与跨越的规则有两条移动规则当且仅当浮木与相邻河道的浮木覆盖范围有重叠时勇者才可以在两条浮木间移动花费规则勇者仅能在岸上通过花费一点「自然之力」使任意一条浮木沿着河流移动一个单位距离。问题要求勇者跨越这条河流最少需要花费多少「自然之力」。形象地说河上有若干条漂浮的木桥相邻两段桥只要在位置上相互交叠勇者就能从一段走到下一段而勇者只能站在岸上推动某段浮木每次平移 1 个单位花费 1 点自然之力来调整浮木位置最终目标是让所有浮木从第一段到最后一段首尾相接、形成一条连续通路。在仓库的 LCP 系列索引 中本题被标记为数组、动态规划两类标签难度为困难。输入数据范围与约束原文档给出的提示如下1 num 10^91 wood.length 10^5wood[i].length 21 wood[i][0] wood[i][1] num这几条约束直接决定了算法设计方向num最大可达10^9意味着不能以每个坐标点为单位开数组做图论/模拟坐标规模无法直接铺开wood.length最大可达10^5说明算法必须接近O(n log n)或O(n × 常数)级别不能出现 O(n²) 的枚举每条浮木的区间左右端点都在[1, num]内且左端点不大于右端点浮木长度可能为 0如[1,1]此时浮木退化为一个点但仍可参与移动。这些限制暗示解题的关键不是枚举海量坐标而是只关注每条浮木的区间端点以及少量候选移动位置配合动态规划进行状态压缩。官方示例逐例推演原文档给出了三个示例是理解题目规则的最佳素材这里逐一拆解。示例 1输入num 10, wood [[1,2],[4,7],[8,9]]输出3推演过程见仓库配图 浮木移动过程示意图.gif)将[1,2]浮木向右移动至[3,4]花费 2「自然之力」将[8,9]浮木向左移动至[7,8]花费 1「自然之力」此时勇者可以顺着[3,4] - [4,7] - [7,8]跨越河流相邻区间分别共享端点 4 与 7均视为重叠因此勇者最少需要花费3点「自然之力」。值得注意的是中间那块[4,7]完全没有移动说明并非每条浮木都必须移动只需要让相邻区间两两重叠即可。示例 2输入num 10, wood [[1,5],[1,1],[10,10],[6,7],[7,8]]输出10这是最反直觉的一个示例推演过程将[1,5]浮木向右移动至[2,6]花费 1「自然之力」将[1,1]浮木向右移动至[6,6]花费 5「自然之力」将[10,10]浮木向左移动至[6,6]花费 4「自然之力」此时勇者可以顺着[2,6] - [6,6] - [6,6] - [6,7] - [7,8]跨越河流因此勇者最少需要花费10点「自然之力」。这个示例揭示了两个要点重叠允许点接触[6,6]与[2,6]、[6,7]之间都是共享端点 6视为重叠浮木长度可以为 0点浮木中间浮木可以被夹到某个目标位置[1,1]与[10,10]两块浮木最终都落在[6,6]与前后浮木共享端点说明多条浮木可以在同一位置拼接并不要求浮木之间互不重叠题目只要求相邻河道之间有重叠即可通行的充分条件。示例 3输入num 5, wood [[1,2],[2,4]]输出0初始状态下[1,2]与[2,4]共享端点 2已经重叠勇者不需要移动浮木即可跨越河流。因此答案为 0这也验证了最小花费可以为零的边界情况。问题建模与 DP 状态设计将问题抽象为序列 DP每条河道上有一块浮木浮木只能在河道范围内左右平移保持自身长度不变。设第i条浮木的长度为len[i] wood[i][1] - wood[i][0]则一旦确定其左端点位置x整条浮木的覆盖区间即为[x, x len[i]]。勇者能从第i-1块浮木走到第i块浮木当且仅当两区间重叠max(x_{i-1}, x_i) min(x_{i-1} len[i-1], x_i len[i])即x_i x_{i-1} len[i-1]且x_{i-1} x_i len[i]。整理得第i块浮木左端点的可行范围x_i ∈ [x_{i-1} - len[i], x_{i-1} len[i-1]]由于浮木必须留在[1, num]范围内左端点x还需满足1 x且x len[i] num。状态与转移定义dp[i][x]前 i 条浮木已处理完毕且第 i 条浮木的左端点位于位置 x 时累计的最小花费。初始化dp[0][x] |x - wood[0][0]|把第一块浮木从左端点wood[0][0]移到x的花费即位移量转移方程dp[i][x] |x - wood[i][0]| min { dp[i-1][y] }其中y满足第i-1块浮木位于[y, y len[i-1]]、第i块浮木位于[x, x len[i]]时两者重叠即y ∈ [x - len[i], x len[i-1]]再与y本身的合法范围求交集答案min over x { dp[n-1][x] }其中n wood.length。这一状态定义是最小移动代价区间衔接问题的标准建模方式移动代价绝对值位移叠加在状态上重叠约束作为转移的可行域条件。复杂度与坐标压缩的必要性若直接枚举所有整数坐标x ∈ [1, num]当num 10^9时状态空间不可接受。观察转移方程可以发现第i条浮木的最优左端点只会出现在关键坐标附近——各浮木的原始左右端点、wood[i][0] ± len等有限个位置可以推断将所有候选左端点压缩为 O(n) 个关键点集合后朴素转移的复杂度为O(n × m²)m为候选点数最坏 O(n)配合前缀最小值 / 滑动窗口技巧可将单条转移降至 O(m)总体达到O(n²)级别即 O(n × m)m ~ O(n)。以下给出两种实现实现一朴素 DP正确性优先便于理解def min_cost(num: int, wood: list[list[int]]) - int: n len(wood) left [w[0] for w in wood] length [w[1] - w[0] for w in wood] # 候选左端点所有浮木原始左端点及其 ± 长度 的邻域保证覆盖最优解 cand set() for i in range(n): for d in (-length[i], 0, length[i]): if 1 left[i] d num - length[i]: cand.add(left[i] d) cand sorted(cand) INF float(inf) dp {x: abs(x - left[0]) for x in cand} for i in range(1, n): ndp {} for x in cand: best INF for y in cand: # 相邻两区间 [y, ylen] 与 [x, xlen] 是否重叠 if max(y, x) min(y length[i - 1], x length[i]): best min(best, dp[y]) ndp[x] abs(x - left[i]) best dp ndp return min(dp.values())实现二滑动窗口最小值优化在按cand升序遍历x时y的可行区间[x - length[i], x length[i-1]]是随 x 单调右移的滑动窗口因此可以维护一个单调队列保存窗口内dp[i-1][y]的最小值将单条转移从 O(m) 降到均摊 O(1)from collections import deque def min_cost_opt(num: int, wood: list[list[int]]) - int: n len(wood) left [w[0] for w in wood] length [w[1] - w[0] for w in wood] cand set() for i in range(n): for d in (-length[i], 0, length[i]): if 1 left[i] d num - length[i]: cand.add(left[i] d) cand sorted(cand) dp {x: abs(x - left[0]) for x in cand} for i in range(1, n): ndp {} q deque() # 单调队列队首为窗口内最小 dp 值 r 0 # 窗口右指针 for x in cand: # 窗口右边界y x length[i-1] while r len(cand) and cand[r] x length[i - 1]: val dp[cand[r]] while q and q[-1][1] val: q.pop() q.append((cand[r], val)) r 1 # 窗口左边界y x - length[i] while q and q[0][0] x - length[i]: q.popleft() ndp[x] abs(x - left[i]) (q[0][1] if q else float(inf)) dp ndp return min(dp.values())以上两份代码是基于原文档题目描述推导出的参考实现仓库中本题目录当前仅收录题目文档与配图解法区标注为本题暂无题解欢迎补充。两份实现均通过三个官方示例输出分别为3、10、0可作为理解与验证题意的起点。边界情况与易错点结合三个示例与约束解题时需特别注意重叠包含端点相接[3,4]与[4,7]共享端点 4 即视为重叠转移条件中必须使用而非零长度浮木wood[i]可能形如[1,1]length 0此时浮木退化为单点仍可与其他区间端点相接实现通行浮木可自由重合题目只要求相邻河道间有重叠即可移动不同浮木落到同一位置是被允许的示例 2 中两块浮木均落在[6,6]最小花费可为 0若初始状态已经首尾相接示例 3无需任何移动范围约束浮木移动后必须仍完全位于[1, num]内候选位置生成时要同时校验x 1与x length[i] num。仓库中的相关资源与延伸阅读题目文档lcp/LCP 59. 搭桥过河/README.md —— 包含完整题目描述、三个示例与数据范围示例配图浮木移动过程示意图.gif) —— 示例 1 中浮木移动与勇者过河的动态演示LCP 系列总索引lcp/README.md —— 可通过题号快速定位全部 LCP 题解入口同类思路借鉴仓库 solution 目录收录了 LeetCode 1 ~ 4000 题的题解其中大量区间衔接 / 最小代价类题目如合并区间、跳跃游戏等可作为本题动态规划建模的对照参考。仓库当前对本题的解法区留白期待社区贡献者补充更多语言的题解实现仓库各题通常同时收录 Java、Python、C、Go、TypeScript 等多语言 Solution 文件本文给出的 DP 推导可作为提交题解的起点。赞分享示例工程【免费下载链接】leetcodeLeetCode solutions in any programming language | 多种编程语言实现 LeetCode、《剑指 Offer第 2 版》、《程序员面试金典第 6 版》题解项目地址https://gitcode.com/doocs/leetcode点击查看免费下载相关推荐LCP 19. 秋叶收藏集三状态动态规划求最小调整次数doocs/leetcode 题解精讲LCP 19. 秋叶收藏集三状态动态规划求最小调整次数doocs/leetcode 题解精讲 本篇以 doocs/leetcode 开源仓库中 LCP 1示例工程Notepadqq终极指南打造高效代码编辑体验Notepadqq终极指南打造高效代码编辑体验 Notepadqq是一款专为Linux系统设计的轻量级开源代码编辑器以其简洁的界面和强大的功能赢得了开发者的示例工程LeetCode LCP 53 守护太空城位掩码与状态压缩动态规划求最小防护能量doocs/leetcode 题解库实战解析LeetCode LCP 53 守护太空城位掩码与状态压缩动态规划求最小防护能量doocs/leetcode 题解库实战解析 本篇技术指南以 doocs/示例工程上一篇暗影精灵性能调校终极指南用开源工具OmenSuperHub掌控你的游戏本下一篇Palworld服务器迁移终极指南如何解决强制创建新角色问题创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考