【贪心算法】实战解析:分发饼干、最优除法、跳跃游戏系列问题详解
原文地址: https://88box.top 生成时间: 2026-05-20 09:39:41
【贪心算法】(经典实战应用解析(四):分发饼干、最优除法、跳跃游戏、跳跃游戏Ⅱ、加油站) - hey99 知识搜索引擎
精选文章
【贪心算法】(经典实战应用解析(四):分发饼干、最优除法、跳跃游戏、跳跃游戏Ⅱ、加油站)
本文摘要: 本文深入探讨了贪心算法在经典题目中的应用,通过分发饼干、最优除法、跳跃游戏等案例解析贪心策略的实现。文章首先介绍了贪心算法的核心思想——通过局部最优选择达到全局最优,并强调在实际应用中需要准确判断贪心策略的合理性。以分发饼干问题为例,详细讲解了排序贪心策略的实现过程:先对胃口和饼干数组排序,然后用最小饼干满足最小胃口,最大化满足孩子数量。代码实现展示了双指针遍历和条件判断的具体应用。文章还提供了完整的测试代码,帮助读者理解算法在实际场景中的运用。
更新于 2026-05-20 01:07
🔥承渊政道:
个人主页
❄️个人专栏:
《C语言基础语法知识》
《数据结构与算法》
《C++知识内容》
《Linux系统知识》
《算法刷题指南》
《测评文章活动推广》
《大模型语言路线学习》
✨逆境不吐心中苦,顺境不忘来时路!✨
🎬 博主简介:
在算法学习中,贪心算法是一类非常经典且高频出现的解题思想.它的核心在于:每一步都选择当前看起来最优的方案,并期望通过局部最优最终得到全局最优.虽然这种思想听起来简单,但真正应用到具体题目中时,往往需要我们准确判断"贪心策略"是否成立,以及如何设计合理的选择规则.本文将继续围绕贪心算法展开,通过几个经典实战题目进行深入解析,包括:
分发饼干、最优除法、跳跃游戏、跳跃游戏Ⅱ、加油站
.这些题目覆盖了排序贪心、区间推进、最远可达范围、步数优化以及环形路径判断等常见场景,能够帮助我们进一步理解贪心算法在不同问题中的应用方式.通过本文的学习,希望你不仅能够掌握这些经典题目的解法,更能逐步建立起分析贪心问题的思维框架:如何寻找局部最优,如何证明贪心选择的合理性,以及如何将复杂问题转化为清晰高效的算法实现.废话不多说,下面跟着小编的节奏🎵一起去疯狂的学习吧!
目录
1.分发饼干(OJ题)
2.最优除法(OJ题)
3.跳跃游戏(OJ题)
4.跳跃游戏Ⅱ(OJ题)
5.加油站(OJ题)
1.分发饼干(OJ题)
解法(贪心):
贪心策略
:先将两个数组排序.针对胃口较小的孩子,从小到大挑选饼干:
i. 如果当前饼干能满足,直接喂(最小的饼干都能满足,不要浪费大饼干);
ii. 如果当前饼干不能满足,放弃这个饼干,去检测下一个饼干(这个饼干连最小胃口的孩子都无法满足,更别提那些胃口大的孩子了).
核心代码
class
Solution
{
public
:
//g: 孩子的胃口数组 s: 饼干的尺寸数组
int
findContentChildren
(
vector
<
int
&
g
,
vector
<
int
&
s
)
{
//核心贪心策略:用最小的饼干满足最小的胃口,最大化满足的孩子数量
//1.对孩子胃口数组 升序排序
sort
(
g
.
begin
(
)
,
g
.
end
(
)
)
;
//2.对饼干尺寸数组 升序排序
sort
(
s
.
begin
(
)
,
s
.
end
(
)
)
;
//ret:记录最终能满足的孩子数量
//n:饼干数组的长度,简化后续代码书写
int
ret
=
0
,
n
=
s
.
size
(
)
;
//双指针遍历:
//i:指向当前待满足的孩子(从胃口最小的开始)
//j:指向当前尝试分配的饼干(从尺寸最小的开始)
//循环条件:孩子没遍历完 且 饼干没遍历完
for
(
int
i
=
0
,
j
=
0
;
i
<
g
.
size
(
)
&&
j
<
n
;
i
++
,
j
++
)
{
//内层循环:找到第一块 尺寸 >= 当前孩子胃口 的饼干
//如果当前饼干太小,就换下一块,直到找到合适的饼干或遍历完所有饼干
while
(
j
<
n
&&
s
[
j
]
<
g
[
i
]
)
j
++
;
//如果找到了合适的饼干,满足的孩子数量+1
if
(
j
<
n
)
ret
++
;
}
//返回最多能满足的孩子数量
return
ret
;
}
}
;
完整测试代码
include
include
include
using
namespace
std
;
class
Solution
{
public
:
// g: 孩子的胃口数组 s: 饼干的尺寸数组
int
findContentChildren
(
vector
<
int
&
g
,
vector
<
int
&
s
)
{
// 核心贪心策略:用最小的饼干满足最小的胃口,最大化满足的孩子数量
// 1. 对孩子胃口数组升序排序
sort
(
g
.
begin
(
)
,
g
.
end
(
)
)
;
// 2. 对饼干尺寸数组升序排序
sort
(
s
.
begin
(
)
,
s
.
end
(
)
)
;
// ret:记录最终能满足的孩子数量
// n:饼干数组的长度
int
ret
=
0
,
n
=
s
.
size
(
)
;
// 双指针遍历
// i:指向当前待满足的孩子
// j:指向当前尝试分配的饼干
for
(
int
i
=
0
,
j
=
0
;
i
<
g
.
size
(
)
&&
j
<
n
;
i
++
,
j
++
)
{
// 找到第一块尺寸 >= 当前孩子胃口的饼干
while
(
j
<
n
&&
s
[
j
]
<
g
[
i
]
)
j
++
;
// 如果找到了合适的饼干,满足的孩子数量 +1
if
(
j
<
n
)
ret
++
;
}
return
ret
;
}
}
;
void
printVector
(
const
vector
<
int
&
nums
)
{
cout
<<
"["
;
for
(
size_t i
=
0
;
i
<
nums
.
size
(
)
;
i
++
)
{
cout
<<
nums
[
i
]
;
if
(
i
!=
nums
.
size
(
)
-
1
)
cout
<<
", "
;
}
cout
<<
"]"
;
}
int
main
(
)
{
Solution sol
;
vector
<
pair
<
vector
<
int
,
vector
<
int
testCases
=
{
{
{
1
,
2
,
3
}
,
{
1
,
1
}
}
,
// 只能满足 1 个孩子
{
{
1
,
2
}
,
{
1
,
2
,
3
}
}
,
// 可以满足 2 个孩子
{
{
1
,
2
,
3
}
,
{
3
}
}
,
// 只能满足胃口为 1 的一个孩子
{
{
1
,
1
,
1
}
,
{
1
,
1
,
1
}
}
,
// 全部满足
{
{
2
,
3
,
4
}
,
{
1
,
1
,
1
}
}
,
// 一个都不能满足
{
{
10
}
,
{
10
}
}
,
// 单个孩子,刚好满足
{
{
10
}
,
{
5
}
}
,
// 单个孩子,不能满足
{
{
}
,
{
1
,
2
,
3
}
}
,
// 没有孩子,结果 0
{
{
1
,
2
,
3
}
,
{
}
}
,
// 没有饼干,结果 0
{
{
5
,
1
,
3
}
,
{
1
,
2
,
3
,
4
}
}
,
// 无序输入,排序后满足 3 个
{
{
1
,
2
,
2
,
3
}
,
{
1
,
1
,
2
,
2
}
}
,
// 包含重复元素,满足 3 个
{
{
4
,
5
,
6
}
,
{
3
,
4
,
5
,
6
}
}
,
// 满足 3 个
{
{
2
,
2
,
3
}
,
{
1
,
2
,
2
,
2
,
3
}
}
// 饼干较多,满足 3 个
}
;
for
(
int
i
=
0
;
i
<
testCases
.
size
(
)
;
i
++
)
{
vector
<
int
g
=
testCases
[
i
]
.
first
;
vector
<
int
s
=
testCases
[
i
]
.
second
;
cout
<<
"测试用例 "
<<
i
+
1
<<
":"
<<
endl
;
cout
<<
"孩子胃口数组 g:"
;
printVector
(
g
)
;
cout
<<
endl
;
cout
<<
"饼干尺寸数组 s:"
;
printVector
(
s
)
;
cout
<<
endl
;
cout
<<
"最多能满足的孩子数量:"
;
cout
<<
sol
.
findContentChildren
(
g
,
s
)
<<
endl
;
cout
<<
"------------------------"
<<
endl
;
}
return
0
;
}
2.最优除法(OJ题)
解法(贪心):
贪心策略
:在最终的结果中,前两个数的位置是无法改变的.因为每一个数的都是大于等于 2 的,为了让结果更大,我们应该尽可能的把剩下的数全都放在分子上.
核心代码
class
Solution
{
public
:
//输入:正整数数组 nums
//输出:能得到最大结果的除法表达式字符串
string
optimalDivision
(
vector
<
int
&
nums
)
{
//获取数组的长度
int
n
=
nums
.
size
(
)
;
//边界情况1:数组只有1个数字,直接返回该数字的字符串形式
if
(
n
==
1
)
{
return
to_string
(
nums
[
0
]
)
;
}
//边界情况2:数组有2个数字,直接拼接为 a/b 即可(无括号必要)
if
(
n
==
2
)
{
return
to_string
(
nums
[
0
]
)
+
"/"
+
to_string
(
nums
[
1
]
)
;
}
//核心逻辑:数组长度 ≥3 时,构造格式:nums[0]/(nums[1]/nums[2]/.../nums[n-1])
//初始化结果字符串:开头为 第一个数/(第二个数
string ret
=
to_string
(
nums
[
0
]
)
+
"/("
+
to_string
(
nums
[
1
]
)
;
//遍历剩余数字(从第三个开始),依次拼接 /数字
for
(
int
i
=
2
;
i
<
n
;
i
++
)
{
ret
+=
"/"
+
to_string
(
nums
[
i
]
)
;
}
//最后补上右括号,完成表达式构造
ret
+=
")"
;
//返回最终构造的字符串
return
ret
;
}
}
;
完整测试代码
include
include
include
using
namespace
std
;
class
Solution
{
public
:
// 输入:正整数数组 nums
// 输出:能得到最大结果的除法表达式字符串
string
optimalDivision
(
vector
<
int
&
nums
)
{
// 获取数组的长度
int
n
=
nums
.
size
(
)
;
// 边界情况1:数组只有1个数字,直接返回该数字的字符串形式
if
(
n
==
1
)
{
return
to_string
(
nums
[
0
]
)
;
}
// 边界情况2:数组有2个数字,直接拼接为 a/b 即可
if
(
n
==
2
)
{
return
to_string
(
nums
[
0
]
)
+
"/"
+
to_string
(
nums
[
1
]
)
;
}
// 核心逻辑:数组长度 >= 3 时
// 构造格式:nums[0]/(nums[1]/nums[2]/.../nums[n-1])
string ret
=
to_string
(
nums
[
0
]
)
+
"/("
+
to_string
(
nums
[
1
]
)
;
// 遍历剩余数字,从第三个开始,依次拼接 /数字
for
(
int
i
=
2
;
i
<
n
;
i
++
)
{
ret
+=
"/"
+
to_string
(
nums
[
i
]
)
;
}
// 最后补上右括号
ret
+=
")"
;
return
ret
;
}
}
;
void
printVector
(
const
vector
<
int
&
nums
)
{
cout
<<
"["
;
for
(
size_t i
=
0
;
i
<
nums
.
size
(
)
;
i
++
)
{
cout
<<
nums
[
i
]
;
if
(
i
!=
nums
.
size
(
)
-
1
)
cout
<<
", "
;
}
cout
<<
"]"
;
}
int
main
(
)
{
Solution sol
;
vector
<
vector
<
int
testCases
=
{
{
1000
,
100
,
10
,
2
}
,
// 经典示例:1000/(100/10/2)
{
2
,
3
,
4
}
,
// 三个数字:2/(3/4)
{
2
}
,
// 只有一个数字:2
{
2
,
3
}
,
// 两个数字:2/3
{
6
,
2
,
3
,
4
}
,
// 四个数字:6/(2/3/4)
{
10
,
5
,
2
}
,
// 三个数字:10/(5/2)
{
1
,
2
,
3
,
4
,
5
}
,
// 多个数字:1/(2/3/4/5)
{
9
,
8
,
7
,
6
,
5
}
,
// 多个数字
{
100
,
10
,
5
}
,
// 100/(10/5)
{
50
,
25
}
,
// 50/25
{
7
}
// 7
}
;
for
(
int
i
=
0
;
i
<
testCases
.
size
(
)
;
i
++
)
{
vector
<
int
nums
=
testCases
[
i
]
;
cout
<<
"测试用例 "
<<
i
+
1
<<
":"
;
printVector
(
nums
)
;
cout
<<
endl
;
cout
<<
"最优除法表达式:"
;
cout
<<
sol
.
optimalDivision
(
nums
)
<<
endl
;
cout
<<
"------------------------"
<<
endl
;
}
return
0
;
}
3.跳跃游戏(OJ题)
解法(贪心):
贪心策略
:遍历数组,维护当前能跳跃到的最远位置.
i. 如果当前位置超出了最远可达位置,说明无法到达,直接失败;
ii. 否则更新最远跳跃位置;
iii. 最远位置覆盖最后一个下标,直接成功.
核心代码
class
Solution
{
public
:
bool
canJump
(
vector
<
int
&
nums
)
{
//变量定义:
//left:当前能到达区间的左边界
//right:当前能到达区间的右边界
//maxPos:遍历后能跳到的最远位置
//n:数组长度,最后一个下标为 n-1
int
left
=
0
,
right
=
0
,
maxPos
=
0
,
n
=
nums
.
size
(
)
;
//循环条件:当前区间有效(左边界 ≤ 右边界),说明还有位置可以遍历
while
(
left
<=
right
)
{
//提前终止:如果最远能到达的位置已经覆盖最后一个下标,直接返回成功
if
(
maxPos
=
n
-
1
)
{
return
true
;
}
//遍历当前区间 [left, right] 内的所有位置
//计算每个位置能跳到的最远位置 i + nums[i],更新全局最远位置 maxPos
for
(
int
i
=
left
;
i
<=
right
;
i
++
)
{
maxPos
=
max
(
maxPos
,
nums
[
i
]
+
i
)
;
}
//更新下一轮遍历的区间:
//左边界 = 原右边界 + 1(新的未遍历区域)
//右边界 = 最新的最远可达位置
left
=
right
+
1
;
right
=
maxPos
;
}
//循环结束仍未到达最后一个下标,返回失败
return
false
;
}
}
;
完整测试代码
include
include
include
using
namespace
std
;
class
Solution
{
public
:
bool
canJump
(
vector
<
int
&
nums
)
{
// 变量定义:
// left:当前能到达区间的左边界
// right:当前能到达区间的右边界
// maxPos:遍历后能跳到的最远位置
// n:数组长度,最后一个下标为 n - 1
int
left
=
0
,
right
=
0
,
maxPos
=
0
,
n
=
nums
.
size
(
)
;
// 循环条件:当前区间有效,说明还有位置可以遍历
while
(
left
<=
right
)
{
// 提前终止:如果最远能到达的位置已经覆盖最后一个下标,直接返回成功
if
(
maxPos
=
n
-
1
)
{
return
true
;
}
// 遍历当前区间 [left, right] 内的所有位置
for
(
int
i
=
left
;
i
<=
right
;
i
++
)
{
maxPos
=
max
(
maxPos
,
nums
[
i
]
+
i
)
;
}
// 更新下一轮遍历的区间
left
=
right
+
1
;
right
=
maxPos
;
}
// 循环结束仍未到达最后一个下标,返回失败
return
false
;
}
}
;
void
printVector
(
const
vector
<
int
&
nums
)
{
cout
<<
"["
;
for
(
size_t i
=
0
;
i
<
nums
.
size
(
)
;
i
++
)
{
cout
<<
nums
[
i
]
;
if
(
i
!=
nums
.
size
(
)
-
1
)
cout
<<
", "
;
}
cout
<<
"]"
;
}
void
printBool
(
bool
result
)
{
cout
<<
(
result
?
"true"
:
"false"
)
;
}
int
main
(
)
{
Solution sol
;
vector
<
vector
<
int
testCases
=
{
{
2
,
3
,
1
,
1
,
4
}
,
// 可以到达最后一个下标
{
3
,
2
,
1
,
0
,
4
}
,
// 无法越过 0
{
0
}
,
// 只有一个元素,已经在终点
{
1
,
0
}
,
// 可以从 0 跳到 1
{
0
,
1
}
,
// 无法从 0 跳出
{
2
,
0
,
0
}
,
// 可以直接跳到最后
{
1
,
1
,
1
,
1
}
,
// 每次跳一步,可以到达
{
4
,
0
,
0
,
0
,
0
}
,
// 第一步可以覆盖终点
{
1
,
2
,
0
,
1
}
,
// 可以到达
{
2
,
5
,
0
,
0
}
,
// 可以到达
{
1
,
0
,
1
,
0
}
,
// 卡在下标 1,无法到达
{
2
,
0
,
2
,
0
,
1
}
,
// 可以到达
{
5
,
4
,
3
,
2
,
1
,
0
,
0
}
,
// 无法到达最后一个 0
{
}
// 空数组,当前代码会返回 true
}
;
for
(
int
i
=
0
;
i
<
testCases
.
size
(
)
;
i
++
)
{
vector
<
int
nums
=
testCases
[
i
]
;
cout
<<
"测试用例 "
<<
i
+
1
<<
":"
;
printVector
(
nums
)
;
cout
<<
endl
;
cout
<<
"是否可以到达最后一个下标:"
;
printBool
(
sol
.
canJump
(
nums
)
)
;
cout
<<
endl
;
cout
<<
"------------------------"
<<
endl
;
}
return
0
;
}
4.跳跃游戏Ⅱ(OJ题)
解法(动态规划 + 类似层序遍历):
动态规划
:
a. 状态表示:
dp[i]
表示从 0 位置开始,到达
i
位置时候的最小跳跃次数
b. 状态转移方程:
对于
dp[i]
,我们遍历
0 ~ i - 1
区间(用指针
j
表示),只要能够从
j
位置跳到
i
位置(
nums[j] + j >= i
),我们就用
dp[j] + 1
更新
dp[i]
里面的值,找到所有情况下的最小值即可.
类似层序遍历的过程
:
用类似层序遍历的过程,将第
i
次跳跃的起始位置和结束位置找出来,用这次跳跃的情况,更新出下一次跳跃的起始位置和终止位置.
这样循环往复,就能更新出到达
n - 1
位置的最小跳跃步数.
核心代码
class
Solution
{
public
:
int
jump
(
vector
<
int
&
nums
)
{
//变量定义:
//left:当前遍历区间的左边界
//right:当前遍历区间的右边界
//maxPos:当前能跳跃到的最远位置
//ret:记录最少跳跃次数
//n:数组长度,最后一个下标为 n-1
int
left
=
0
,
right
=
0
,
maxPos
=
0
,
ret
=
0
,
n
=
nums
.
size
(
)
;
//循环遍历:当前区间有效(左边界 <= 右边界),说明还有位置可以处理
//兜底判断:防止极端情况无法到达终点
while
(
left
<=
right
)
{
//提前终止:如果当前最远位置已经覆盖最后一个下标
//直接返回当前的跳跃次数,无需继续遍历
if
(
maxPos
=
n
-
1
)
{
return
ret
;
}
//核心逻辑:遍历当前区间 [left, right] 内的所有位置
//计算每个位置能跳到的最远位置,更新全局最远可达位置 maxPos
for
(
int
i
=
left
;
i
<=
right
;
i
++
)
{
maxPos
=
max
(
maxPos
,
nums
[
i
]
+
i
)
;
}
//更新下一轮遍历的区间:
//左边界 = 原右边界 + 1(进入新的一层/区间)
//右边界 = 最新的最远可达位置
left
=
right
+
1
;
right
=
maxPos
;
//每完成一层区间的遍历,代表完成一次跳跃,次数+1
ret
++
;
}
//兜底返回:题目保证一定能到达终点,此情况理论上不会触发
return
-
1
;
}
}
;
完整测试代码
include
include
include
using
namespace
std
;
class
Solution
{
public
:
int
jump
(
vector
<
int
&
nums
)
{
// 变量定义:
// left:当前遍历区间的左边界
// right:当前遍历区间的右边界
// maxPos:当前能跳跃到的最远位置
// ret:记录最少跳跃次数
// n:数组长度,最后一个下标为 n - 1
int
left
=
0
,
right
=
0
,
maxPos
=
0
,
ret
=
0
,
n
=
nums
.
size
(
)
;
// 循环遍历:当前区间有效,说明还有位置可以处理
while
(
left
<=
right
)
{
// 提前终止:如果当前最远位置已经覆盖最后一个下标
if
(
maxPos
=
n
-
1
)
{
return
ret
;
}
// 遍历当前区间 [left, right] 内的所有位置
for
(
int
i
=
left
;
i
<=
right
;
i
++
)
{
maxPos
=
max
(
maxPos
,
nums
[
i
]
+
i
)
;
}
// 更新下一轮遍历的区间
left
=
right
+
1
;
right
=
maxPos
;
// 每完成一层区间遍历,代表完成一次跳跃
ret
++
;
}
// 兜底返回:如果无法到达终点,返回 -1
return
-
1
;
}
}
;
void
printVector
(
const
vector
<
int
&
nums
)
{
cout
<<
"["
;
for
(
size_t i
=
0
;
i
<
nums
.
size
(
)
;
i
++
)
{
cout
<<
nums
[
i
]
;
if
(
i
!=
nums
.
size
(
)
-
1
)
cout
<<
", "
;
}
cout
<<
"]"
;
}
int
main
(
)
{
Solution sol
;
vector
<
vector
<
int
testCases
=
{
{
2
,
3
,
1
,
1
,
4
}
,
// 最少跳 2 次:0 -> 1 -> 4
{
2
,
3
,
0
,
1
,
4
}
,
// 最少跳 2 次:0 -> 1 -> 4
{
1
,
1
,
1
,
1
}
,
// 每次只能跳一步,最少跳 3 次
{
0
}
,
// 只有一个元素,不需要跳,结果 0
{
1
,
0
}
,
// 跳 1 次到终点
{
2
,
0
,
0
}
,
// 跳 1 次直接到终点
{
3
,
2
,
1
}
,
// 跳 1 次可以覆盖终点
{
1
,
2
,
1
,
1
,
1
}
,
// 最少跳 3 次
{
4
,
1
,
1
,
3
,
1
,
1
,
1
}
,
// 最少跳 2 次:0 -> 3 -> 6
{
5
,
4
,
3
,
2
,
1
,
0
}
,
// 跳 1 次可覆盖终点
{
2
,
1
,
2
,
1
,
1
}
,
// 最少跳 2 次:0 -> 2 -> 4
{
1
,
2
,
3
}
,
// 最少跳 2 次:0 -> 1 -> 2
{
3
,
0
,
0
,
0
}
,
// 最少跳 1 次
{
1
,
0
,
1
}
,
// 无法到达,返回 -1
{
}
// 空数组,当前逻辑返回 0
}
;
for
(
int
i
=
0
;
i
<
testCases
.
size
(
)
;
i
++
)
{
vector
<
int
nums
=
testCases
[
i
]
;
cout
<<
"测试用例 "
<<
i
+
1
<<
":"
;
printVector
(
nums
)
;
cout
<<
endl
;
cout
<<
"最少跳跃次数:"
;
cout
<<
sol
.
jump
(
nums
)
<<
endl
;
cout
<<
"------------------------"
<<
endl
;
}
return
0
;
}
5.加油站(OJ题)
解法(暴力解法 -> 贪心):
暴力解法
:
a. 依次枚举所有的起点;
b. 从起点开始,模拟一遍加油的流程
贪心优化
:
我们发现,当从
i
位置出发,走了
step
步之后,如果失败了.那么
[i, i + step]
这个区间内任意一个位置作为起点,都不可能环绕一圈.
因此我们枚举的下一个起点,应该是
i + step + 1
.
核心代码
class
Solution
{
public
:
//gas:每个加油站的汽油量 cost:到达下一站的耗油量
int
canCompleteCircuit
(
vector
<
int
&
gas
,
vector
<
int
&
cost
)
{
int
n
=
gas
.
size
(
)
;
//加油站的总数量
//外层循环:依次枚举每一个加油站作为起始点
for
(
int
i
=
0
;
i
<
n
;
i
++
)
{
int
rest
=
0
;
//记录当前剩余油量(净收益:加油量-耗油量)
int
step
=
0
;
//记录从起点出发已经行走的步数
//内层循环:尝试从起点i出发,绕行一圈
for
(
;
step
<
n
;
step
++
)
{
//环形路线计算当前到达的加油站下标
int
index
=
(
i
+
step
)
%
n
;
//更新剩余油量:加上当前加油站油量,减去到下一站的耗油量
rest
=
rest
+
gas
[
index
]
-
cost
[
index
]
;
//剩余油量 < 0,无法继续前行,跳出当前起点的尝试
if
(
rest
<
0
)
break
;
}
//成功走完n步(绕行一圈),返回当前起点i
if
(
rest
=
0
)
return
i
;
//贪心优化核心:
//从i到i+step之间的所有点都无法作为起点,直接跳过,减少循环次数
i
=
i
+
step
;
}
//遍历完所有起点都无法绕行一周,返回-1
return
-
1
;
}
}
;
完整测试代码
include
include
using
namespace
std
;
class
Solution
{
public
:
// gas:每个加油站的汽油量 cost:到达下一站的耗油量
int
canCompleteCircuit
(
vector
<
int
&
gas
,
vector
<
int
&
cost
)
{
int
n
=
gas
.
size
(
)
;
// 加油站的总数量
// 外层循环:依次枚举每一个加油站作为起始点
for
(
int
i
=
0
;
i
<
n
;
i
++
)
{
int
rest
=
0
;
// 记录当前剩余油量
int
step
=
0
;
// 记录从起点出发已经行走的步数
// 内层循环:尝试从起点 i 出发,绕行一圈
for
(
;
step
<
n
;
step
++
)
{
// 环形路线计算当前到达的加油站下标
int
index
=
(
i
+
step
)
%
n
;
// 更新剩余油量
rest
=
rest
+
gas
[
index
]
-
cost
[
index
]
;
// 剩余油量 < 0,无法继续前行
if
(
rest
<
0
)
break
;
}
// 成功走完 n 步,返回当前起点 i
if
(
rest
=
0
)
return
i
;
// 从 i 到 i + step 之间的所有点都无法作为起点,直接跳过
i
=
i
+
step
;
}
return
-
1
;
}
}
;
void
printVector
(
const
vector
<
int
&
nums
)
{
cout
<<
"["
;
for
(
size_t i
=
0
;
i
<
nums
.
size
(
)
;
i
++
)
{
cout
<<
nums
[
i
]
;
if
(
i
!=
nums
.
size
(
)
-
1
)
cout
<<
", "
;
}
cout
<<
"]"
;
}
int
main
(
)
{
Solution sol
;
vector
<
pair
<
vector
<
int
,
vector
<
int
testCases
=
{
{
{
1
,
2
,
3
,
4
,
5
}
,
{
3
,
4
,
5
,
1
,
2
}
}
,
// 可以从下标 3 出发
{
{
2
,
3
,
4
}
,
{
3
,
4
,
3
}
}
,
// 无法绕行一周
{
{
5
,
1
,
2
,
3
,
4
}
,
{
4
,
4
,
1
,
5
,
1
}
}
,
// 可以从下标 4 出发
{
{
3
,
3
,
4
}
,
{
3
,
4
,
4
}
}
,
// 总油量小于总消耗,返回 -1
{
{
1
}
,
{
1
}
}
,
// 单个加油站,刚好可以
{
{
2
}
,
{
3
}
}
,
// 单个加油站,不可以
{
{
4
}
,
{
3
}
}
,
// 单个加油站,可以
{
{
6
,
1
,
4
,
3
,
5
}
,
{
3
,
8
,
2
,
4
,
2
}
}
,
// 可以从下标 2 出发
{
{
2
,
2
,
2
}
,
{
2
,
2
,
2
}
}
,
// 每站刚好抵消,可以从下标 0 出发
{
{
1
,
0
,
1
,
0
,
1
}
,
{
0
,
1
,
0
,
1
,
0
}
}
,
// 可以从下标 0 出发
{
{
0
,
0
,
0
}
,
{
0
,
0
,
0
}
}
,
// 全为 0,可以从下标 0 出发
{
{
10
,
1
,
1
,
1
}
,
{
1
,
2
,
2
,
2
}
}
,
// 可以从下标 0 出发
{
{
1
,
1
,
10
,
1
}
,
{
2
,
2
,
1
,
2
}
}
,
// 可以从下标 2 出发
{
{
}
,
{
}
}
// 空数组,返回 -1
}
;
for
(
int
i
=
0
;
i
<
testCases
.
size
(
)
;
i
++
)
{
vector
<
int
gas
=
testCases
[
i
]
.
first
;
vector
<
int
cost
=
testCases
[
i
]
.
second
;
cout
<<
"测试用例 "
<<
i
+
1
<<
":"
<<
endl
;
cout
<<
"gas 数组:"
;
printVector
(
gas
)
;
cout
<<
endl
;
cout
<<
"cost 数组:"
;
printVector
(
cost
)
;
cout
<<
endl
;
cout
<<
"可完成环路的起点下标:"
;
cout
<<
sol
.
canCompleteCircuit
(
gas
,
cost
)
<<
endl
;
cout
<<
"------------------------"
<<
endl
;
}
return
0
;
}
🚀真正的勇者不是流泪的人,而是含泪奔跑的人!
敬请期待下一篇文章内容的更新【贪心算法】(经典实战应用解析(五):单调递增的数字、坏了的计算器、合并区间、⽆重叠区间、⽤最少数量的箭引爆⽓球)
每日心灵鸡汤: 回头看,轻舟已过万重山;向前看,长路漫漫亦灿灿!
很喜欢一句话:"山海自有归期,风雨自有相逢,意难平终将和解,万事终将如意."这个世界上不存在无法治愈的伤痛,没有不能结束的沉沦,所有失去的会以另一种归来.那些我们现在非常在意的事情,久久放不下的人,可能多年以后再回头,会觉得根本不算什么.就像现在的你不会再纠结以前的事情一样,曾经放不下的人和事,到最后,岁月终究会教你学会轻描淡写.一切都是瞬息,一切都将会过去,调整好心态,别纠结于得失上,请你一定相信,如果事与愿违,那一定是另有安排!
查看原文
🏷 标签: 贪心算法、排序贪心、区间覆盖、跳跃问题、环形路径