news 2026/10/9 15:37:58

高频必考!跳跃游戏 加油站:贪心最反直觉的两步,凭什么是对的?

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
高频必考!跳跃游戏 加油站:贪心最反直觉的两步,凭什么是对的?

💥 两行“跳步”代码,凭什么敢不回头?

先看两段“看起来不太对、但确实对”的代码:

# LC.55 跳跃游戏:一趟扫描,从不回头max_reach=0fori,xinenumerate(nums):ifi>max_reach:returnFalse# ← 凭什么这里失败就整体失败了?max_reach=max(max_reach,i+x)# LC.134 加油站:跌破0就把起点跳到i+1,永不回头foriinrange(n):tank+=gas[i]-cost[i]iftank<0:start=i+1# ← 凭什么0..i里没有一站能做起点?tank=0

这两行代码是贪心里最典型的“跳步”:不是逐个验证候选,而是一次排除一大片。它们的正确性不靠直觉,靠两件东西:

  1. 跳跃游戏:可达集合永远是一个连续前缀[0, maxReach]——这是“决策包容性”;
  2. 加油站:从a出发在b处跌破0,则a…b之间任何一站都到不了b——这是“冗余候选的批量排除”。

今天的目标就是把这两句话从“感觉”变成“证明”。


📦 题目速览(30秒读懂)

题目1:跳跃游戏(LC.55)

非负整数数组nums,每个元素代表最大跳跃长度。从下标0出发,判断能否到达最后一个下标。

示例:[2,3,1,1,4]→true;[3,2,1,0,4]→false
约束:n ≤ 1e4。

题目2:加油站(LC.134)

环形路线,gas[i]是第i站油量,cost[i]是到下一站的耗油。从某站出发(油箱为空),能否绕一圈?返回起点编号或-1(保证解唯一)。

示例:gas=[1,2,3,4,5], cost=[3,4,5,1,2]→ 输出3
约束:n ≤ 1e5。


🧠 核心思路

一、跳跃游戏:覆盖范围贪心

核心洞察:题目说“可以跳最多nums[i]步”,意味着可以跳任意不超过它的步数。因此:

从已可达的任意位置出发,能覆盖到的下标集合,永远是一个连续区间[0, maxReach]。

这意味着“当前能到哪些位置”不需要布尔数组,只需要一个整数maxReach。

算法:

maxReach = 0 for i in 0..n-1: if i > maxReach: return False # 断层:i 不可达 → 后面全不可达 maxReach = max(maxReach, i + nums[i]) if maxReach >= n-1: return True # 提前成功 return True
为什么i > maxReach就能直接判否?(反证)
  1. 循环不变量:扫描到i之前,maxReach= 从0..i-1中任一可达位置出发能到的最远下标。
  2. 若i > maxReach,则所有j < i都满足j + nums[j] <= maxReach < i。
  3. 跳跃只能向右,要到达i或更远,最后一步必然从某个j < i跳过来。
  4. 但所有j < i都够不着i→i及之后全部不可达 → 直接返回False。∎

这就是“决策包容性”:一个标量maxReach包容了所有历史决策的信息。

与DP对比

DP解法:dp[i] = 能否到达i,转移是OR_{j<i}(dp[j] && j+nums[j] >= i),O(n²)时间O(n)空间。

贪心更优的原因:可达集合恒为连续前缀,整个dp[]布尔数组的信息量坍缩成一个整数。DP内层求的max可以增量维护,O(n²)塌成O(n)。实测n=10,000时快2,549倍,空间从O(n)降到O(1)。


二、加油站:两步走 + 两个证明

第一步(充要条件):sum(gas) >= sum(cost)⟺ 有解。

  • 必要性:总油量不够显然无解。
  • 充分性:见下方前缀和证明。

第二步(一次遍历定起点):从0出发累加diff[i] = gas[i] - cost[i],一旦tank < 0,就把起点设为i+1并清空tank,绝不回头验证。

为什么跌破0可以直接跳到i+1?(引理 + 反证)

引理:若从a出发,走到b时油量首次跌破0,则a..b之间任何一站都不能作为起点。

证明:

  1. 设从a出发到达中间站c(a < c <= b)时,油箱剩余T >= 0。
  2. 路线A:从a开到c,油箱有T升,继续往b开。
  3. 路线B:从c作起点,油箱是0升(题目规定出发为空),继续往b开。
  4. 两条路线从c到b路况完全相同,唯一区别是路线A多带了T >= 0升油。
  5. 既然路线A都过不去b,少带油的路线B只会更早趴窝。

一句话:从中间站出发,等于丢掉了前面攒下的余量,只会更差不会更好。

为什么sum(gas) >= sum(cost)就一定有解?(前缀和证明)

记号:A(i) = Σ_{k=0}^{i} (gas[k] - cost[k]),A(-1)=0。

引理A:贪心输出的start满足A(start-1) = min{A(-1), A(0), ..., A(n-1)},即全局最小前缀和的位置。

定理:若total = A(n-1) >= 0,则从start出发可跑完一圈。

证明:从start出发到任意站j的累计剩余油量:

  • 若j >= start:剩余 =A(j) - A(start-1)。因A(start-1)是全局最小,所以>= 0✅
  • 若j < start(已绕回):剩余 =A(j) + total - A(start-1)。同理>= 0✅

全程非负 → 跑完一圈。∎

附加收获:解的唯一性(start = argmin(A) + 1);total < 0时返回-1。


🖼️ 图解算法(手把手走一遍)

跳跃游戏:[2,3,1,1,4](可达)

inums[i]i > maxReach?maxReach更新可达区间
02否max(0, 0+2)=2[0,2]
13否max(2, 1+3)=4[0,4]≥ n-1 →true

跳跃游戏:[3,2,1,0,4](不可达)

inums[i]i > maxReach?maxReach说明
03否3覆盖[0,3]
12否3没扩展
21否3没扩展
30否3死点
444 > 3 ✅—断层!return False

加油站:gas=[1,2,3,4,5], cost=[3,4,5,1,2]

diff = [-2,-2,-2,3,3],总和 = 0 ≥ 0 → 必有解。

idifftank累加跌破?start
0−2−2✅1
1−2−2✅2
2−2−2✅3
3+33否3
4+36否3

答案 = 3✅


💻 代码实现(Python + Java)

Python版

classSolution:# ============ LC.55 跳跃游戏 ============defcanJump(self,nums:List[int])->bool:max_reach=0fori,xinenumerate(nums):ifi>max_reach:returnFalsemax_reach=max(max_reach,i+x)ifmax_reach>=len(nums)-1:returnTruereturnTrue# ============ LC.45 跳跃游戏 II(分层贪心) ============defjump(self,nums:List[int])->int:iflen(nums)<=1:return0jumps,cur_end,farthest=0,0,0foriinrange(len(nums)-1):farthest=max(farthest,i+nums[i])ifi==cur_end:jumps+=1cur_end=farthestifcur_end>=len(nums)-1:breakreturnjumps# ============ LC.134 加油站 ============defcanCompleteCircuit(self,gas:List[int],cost:List[int])->int:total=tank=start=0foriinrange(len(gas)):diff=gas[i]-cost[i]total+=diff tank+=diffiftank<0:start=i+1tank=0return-1iftotal<0elsestart

Java 版

classJumpGameSolution{publicbooleancanJump(int[]nums){intmaxReach=0;for(inti=0;i<nums.length;i++){if(i>maxReach)returnfalse;maxReach=Math.max(maxReach,i+nums[i]);if(maxReach>=nums.length-1)returntrue;}returntrue;}}classGasStationSolution{publicintcanCompleteCircuit(int[]gas,int[]cost){inttotal=0,tank=0,start=0;for(inti=0;i<gas.length;i++){intdiff=gas[i]-cost[i];total+=diff;tank+=diff;if(tank<0){start=i+1;tank=0;}}returntotal<0?-1:start;}}

⚠️防坑提醒:

  • 跳跃游戏:maxReach更新必须在i > maxReach判定之后。
  • LC.45循环i < n-1,不含最后一个元素。
  • 加油站:total和tank是两个独立量,别合并。

实测数据(本机运行)

贪心 vs DP(跳跃游戏,结果一致):

n贪心DP(O(n²))提速
5,0000.000013s0.247s19,405×
10,0000.000839s2.138s2,549×
100,0000.0086s无法跑—

贪心 vs 暴力(加油站,最坏情况):

n贪心暴力 O(n²)提速
2,0000.000106s0.172s1,619×
4,0000.000195s0.688s3,520×

⏱️ 复杂度分析(面试必问)

题目贪心DP/暴力
LC.55 跳跃游戏O(n) / O(1)O(n²) / O(n)
LC.45 跳跃游戏 IIO(n) / O(1)—
LC.134 加油站O(n) / O(1)O(n²) / O(1)

🚀 举一反三:6 道高频变体题

题目变化思路要点
LC.45 跳跃游戏II求最少跳跃次数分层贪心:curEnd/farthest
LC.1306 跳跃游戏III可双向跳变成图遍历(BFS/DFS + visited)
LC.1024 视频拼接片段覆盖[0,T]按start排序 + 覆盖范围贪心
LC.1326 灌溉花园水龙头覆盖全段区间覆盖贪心
LC.435 无重叠区间取舍类按end升序 + 交换论证
LC.452 引爆气球最少箭穿透同骨架,>代替>=

💬 面试追问模拟(提前准备,惊艳全场)

Q1:跳跃游戏的DP解法怎么写?为什么贪心更优?

DP:dp[i] = 能否到达i,转移OR_{j<i}(dp[j] && j+nums[j] >= i),O(n²) / O(n)。贪心更优因为可达集合恒为连续前缀,整个布尔数组坍缩成一个整数。实测快 2,549 倍。

Q2:加油站为什么跌破0就跳到i+1?

引理:若从a出发在b处跌破0,则a..b中任何一站c都不能作起点。
因为从a开到c时还有T >= 0余量,而从c作起点时油箱是0——两条路线在c之后路况相同,但路线B少带了T升油,只会更早趴窝。

Q3:贪心和DP的边界在哪?

判据:局部最优能否推出全局最优,且你能写出证明。写得出交换论证/包容性/前缀和 → 贪心;写不出 → DP。
三条信号:
① DP 状态能压缩成可增量维护的标量 → 可退化贪心;
② 决策有后效性 → 必须DP;
③ 要求输出方案 → 回溯。


🧩 实战小技巧(刷题党必备)

  • 口诀:跳跃看覆盖,一路不回头;加油跌破零,起点跳i+1。
  • 模板:覆盖范围贪心 = 维护一个标量边界;加油站 = total判有解 + tank定位点。
  • 防坑:maxReach更新顺序;LC.45循环i < n-1;total与tank别混。

📈 实际应用场景(不止是刷题)

  • 游戏关卡:判断能否通关、最少步数
  • 资源调度:油箱问题、补给站规划
  • 网络路由:覆盖范围转发
  • 视频拼接:最少片段覆盖完整时间轴

🎁 今日思考题

如果把加油站改成“求所有可行起点”,算法怎么改?
提示:题目保证最多一个解,所以total >= 0时直接返回start即可;若允许多个解,需要找出前缀和A的所有全局最小值位置——想想为什么“全局最小值”和“可行起点”是一回事。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/9 15:30:10

前端表单多选联动实战:动态可选项更新与状态同步清理

1. 表单联动背后的真实需求拆解1.1 从一个典型场景说起做过中后台系统的人大概率都碰过这种需求&#xff1a;一个表单里有一组多选框&#xff0c;用户勾选其中某几项之后&#xff0c;另外几个下拉框或者多选组的可选项要跟着变&#xff0c;甚至某些选项要直接置灰禁用。听起来像…

作者头像 李华
网站建设 2026/10/9 15:30:06

抖音对话生成器原理与实现:从JSON渲染到canvas导出一文读懂

简介&#xff1a;基于HTML、CSS与JavaScript实现的抖音对话生成器项目源码&#xff0c;面向具备一定前端基础、希望快速搭建个性化对话演示工具的开发者。该工具允许使用者自由设定对话内容与头像信息&#xff0c;JavaScript会将前端设置即时更新到页面中&#xff0c;同时提供随…

作者头像 李华
网站建设 2026/10/9 15:28:44

倚天财经指标公式期货策略源码期货指标公式

育龙:(EMA(CLOSE,12) - EMA(CLOSE,26))*100; 指标:EMA(育龙,9); DRAWTEXT(CROSS(育龙,指标),90,话),COLORWHITE; DRAWTEXT(CROSS(指标,育龙),90,糸),COLORYELLOW; DRAWTEXT(CROSS(育龙,指标),60,1),COLORWHITE; DRAWTEXT(CROSS(指标,育龙),60,1),COLORYELLOW; DRAWTEXT(CROSS(育…

作者头像 李华
网站建设 2026/10/9 15:22:55

向上取整符号全解析:从数学定义到编程实战与避坑指南

1. 从一个不起眼的符号说起&#xff1a;向上取整到底在解决什么问题我第一次真正意识到向上取整符号的价值&#xff0c;是在做一个活动报名系统的时候。当时产品经理提了一个需求&#xff1a;每辆车最多坐4个人&#xff0c;现在有37个人要出行&#xff0c;需要安排几辆车&#…

作者头像 李华
网站建设 2026/10/9 15:19:38

Java实现机房动环检测系统:Modbus采集、告警引擎与联动控制

简介&#xff1a;这份基于Java语言的机房动环检测系统设计源码&#xff0c;面向需要构建机房动力环境监控方案的开发者与学习者&#xff0c;可用于实时采集供电、空调、温湿度、消防、漏水等设备数据&#xff0c;并实现异常报警与用户交互。资源包共66个文件&#xff0c;约218K…

作者头像 李华
网站建设 2026/10/9 15:17:27

MySQL 5.7.32 ARM二进制包部署:aarch64环境初始化与避坑指南

简介&#xff1a;mysql-5.7.32-linux-glibc-2.28-aarch64.tar.gz 是为 ARM64&#xff08;AArch64&#xff09;Linux 环境预编译的 MySQL 5.7.32 官方二进制发行包&#xff0c;面向树莓派 4、ARM 云服务器等设备的使用者&#xff0c;可直接部署数据库而无需手动编译。压缩包约 5…

作者头像 李华