前言
贪心算法,顾名思义就是要贪心,古话说得好: "智者虑远,愚者虑近"。贪心需要不看长远利益而是只看眼前最大利益,所以算法的核心思想就是不考虑接下来步骤的影响,我们只考虑当前这一步是我们的"最优解"即可。通过每一步的最优解从而得到全局的"最优解"。
PS:这里的全局"最优解"不一定是所有整体方案中的最优解。因为我们的每一步是没考虑后续影响的。这是一种粗暴简单的方法,看重每一步的最优解法来认为整体的流程都是最优的方法。
经典找零钱问题
让我们再来回顾一下经典的找零钱问题
假设我们有面额为
1元
、5元
、10元
和25元
的硬币。你要找给顾客83元,用最少的硬币数量。
既然我们上面说了要只看前利益,所以我们每次找钱的时候就以结果为用最大的面额,这样就能用最少的数量去找零钱。
思路: 每一次给的硬币都是面额最大的。
- 找83元:拿一枚25元硬币,还剩58元。
- 找58元:拿一枚25元硬币,还剩33元。
- 找33元:拿一枚25元硬币,还剩8元。
- 找8元:拿一枚5元硬币,还剩3元。
- 找3元:拿三枚1元硬币。
最终我们得到了3个25元 ,1个5元 ,3个1元 ,总共7
枚硬币。这在所有组合的方案中确实是最优解。
但是我们也说了贪心算法只是我们通过每一步做最优解希望最终组合的方案是最优解,他大部分情况下可以不错的解决问题,但也不是一个万能的办法。比如下面的问题
面额为
1元
、5元
、10元
和12元
的硬币,现在要找给顾客15元
。
再一次贪心算法 我们每一步组合总是选择面额最大的硬币:
- 找15元 :先拿一枚 12元 硬币,还剩下 3元。
- 找3元 :只能拿三枚 1元 硬币。
现在的组合是1个12元 ,3个1元 ,总共使用了 4
枚硬币,然而就我们的认知来说实际上可以直接给3个5元 ,这样就只需要3
枚硬币了。所以由此可见,贪心算法固然好用,但是有时候他也会不靠谱(所有出现了动态规划)。
做个算法题
既然我们现在了解了贪心算法到底是个啥思想,那么我们来做个算法题融会贯通下。题目出自力扣
题目

题目大意:传入一个数组nums
,从0下标开始,以当前索引为i
,索引对应的值为v
,我们可以选择跳到i+n(n<= v)
的新索引位置,然后以新的索引位置来决定跳到的下一个索引。判断这个数组是否能跳完,能跳完返回true
,否则false
。

分析与实现
- 分析题目第一步我们要确定我们的结果是能否跳出我们的数组,那么我们实际上就是算的当前的
i
+v
是否能大于等于nums
的长度-1
,可得当前可最大跳到的索引maxIndex=nums[i]+i
。 - 我们只需要判断当
maxIndex=nums[i]+i>=nums.length-1
时返回true
即可。 - 因为我们只要保证能跳出这个数组,并且没有求刚好跳出的这种最优解,所以我们可以利用贪心算法的思想,每一次我们只管当前的最大可跳跃到的位置
nums[i]+i
。先不去考虑可能 接下来会造成跳到0
造成无法结束数组,但是实际上如果跳一个小于nums[i]
的值再通过下一个索引去跳就可以绕过0
去避免提前结束的情况。 - 对于我们
3
说的提前结束的情况,我们可以去判断当前我们的索引是否已经超过了最大可以跳到的索引nums[i]+i
即可解决。当我们从当前跳到的最大值也无法超过我们索引值的时候,就代表无法通过新的索引去往下跳了,也就是出现示例2
的那种情况。
代码实现
kotlin
fun canJump(nums: IntArray): Boolean {
//当前可跳到的最大索引
var maxIndex = 0
for ((index, item) in nums.withIndex()) {
//判断当前之前跳到的最大位置是否能到达新的遍历点,如果无法到达证明跳不到这里,直接返回false
if (maxIndex < index) {
return false
}
//每个新的位置都以当前新位置可到达的最大点作为下一个跳达点去和之前的最大可跳达点比较
//如果还没之前跳的远那就以之前的跳达点为主
maxIndex = max(index + item, maxIndex)
}
//当可以遍历完就证明必然可以达到终点
return true
}
看起来我们的结果并没有问题,证明我们使用贪心算法去解决也是正确的。
总
贪心算法是一种很简单粗暴的解题思路,也是用的非常多的一种算法,例如最小生成树、最短路径,找零钱。又比如生活中我们赶飞机或火车要坐多个交通换乘时一般都会在每一个交通选择上先做最快的选择,而不是卡死这个刚好到达的时间,当你面对一个新问题他需要多个步骤时,可以先尝试用贪心算法来思考。如果能证明它的贪心选择是正确的,那么这种方法通常是最简单高效的。😎