【贪心-01】贪心算法原理介绍
目录
- 定义
- 基本思路
- 动态规划比较
- 举例说明-55. 跳跃游戏
一、定义
贪心算法(greedy algorithms)(《算法导论(第三版)》第 16 章也有叙述)的定义:在对问题求解时,总是做出在当前看来是最好的选择。贪心算法不是对所有问题都能得到整体最优解,关键是贪心策略的选择,选择的贪心策略必须具备无后效性,即某个状态以前的过程不会影响以后的状态,只与当前状态有关。
1.1 适应的问题对象:
贪心策略适用的前提是:局部最优策略能导致产生全局最优解。
实际上,贪心算法适用的情况很少。一般对一个问题分析是否适用于贪心算法,可以先选择该问题下的几个实际数据进行分析,就可以做出判断。
1、需要求解准确解:必须证明每一步所作的贪心选择最终导致问题的整体最优解。
2、求解近似解:有时候不需要准确求解,为了加快速度,可以考虑贪心算法求解近似解。
1.2 关键点
没有一般化的规则来说明贪心算法是否最优,策略的选取也没有确定形式,但有两个基本要点:贪心选择性质和最优子结构。
贪心选择性质
第一个关键要素就是贪心选择性质:我们可以做出局部最优选择来构造全局最优解。也就是说,我们在做出选择时,总是以当前的情况为基础做出最优选择的,而不用考虑子问题的解。
这也是和动态规划最大的不同之处。在动态规划中,在每次做出一个选择的时候总是要将所有选择进行比较才能确定到底采用哪一种选择,而这种选择的参考依据是以子问题的解为基础的,所以动态规划总是采用自底向上的方法,先得到子问题的解,再通过子问题的解构造原问题的解。就算是自顶而下的算法也是先求出子问题的解。在贪心算法中,我们总是在原问题的基础上做出一个选择,然后求解剩下的唯一子问题,贪心算法从来都不依赖子问题的解,不过有可能会依赖上一次做出的选择,所以贪心算法是自顶而下的。
最优子结构
如果一个问题的最优解包含其子问题的最优解,那么就称这个问题具有最优子结构性质。
二、基本思路
贪心算法的基本思路是从问题的某一个初始解出发一步一步地进行,根据某个优化测度,每一步都要确保能获得局部最优解。每一步只考虑一个数据,他的选取应该满足局部优化的条件。若下一个数据和部分最优解连在一起不再是可行解时,就不把该数据添加到部分解中,直到把所有数据枚举完,或者不能再添加算法停止
2.1 流程思路
- 建立数学模型来描述问题
- 把求解的问题分成若干个子问题
- 对每个子问题求解,得到子问题的局部最优解
- 把子问题的解局部最优解合成原来问题的一个解
2.2 实现框架
从问题的某一初始解出发:
while (朝给定总目标前进一步)
{
利用可行的决策,求出可行解的一个解元素。
}
由所有解元素组合成问题的一个可行解;
三、动态规划比较
3.1 相同点
- 都是一种推导算法
- 都是分解成子问题来求解,都需要具有最优子结构
3.2不同点
- 贪心算法和动态规划相比,它既不看前面(也就是说它不需要从前面的状态转移过来),也不看后面(无后效性,后面的选择不会对前面的选择有影响),因此贪心算法时间复杂度一般是线性的,空间复杂度是常数级别的。
- 贪心算法的每一次操作都对结果产生直接影响,而动态规划则不是。贪心算法对每个子问题的解决方案都做出选择,不能回退;动态规划则会根据以前的选择结果对当前进行选择,有回退功能。
- 动态规划主要运用于二维(也可以一维)问题,而贪心一般是处理一维问题。
- 动态规划因为要使用子问题顺序结果,因此一般是自底向上,贪心算法没有相关要求。
四、举例说明-55. 跳跃游戏
4.1、问题
给定一个非负整数数组,你最初位于数组的第一个位置。数组中的每个元素代表你在该位置可以跳跃的最大长度。判断你是否能够到达最后一个位置。
示例 1:
输入: [2,3,1,1,4]
输出: true
解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。
示例 2:
输入: [3,2,1,0,4]
输出: false
解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。
4.2、理解
子问题:设想一下,对于数组中的任意一个位置 y,我们如何判断它是否可以到达?根据题目的描述,只要存在一个位置 x,它本身可以到达,并且它跳跃的最大长度为 x + nums[x],这个值大于等于 y,即 x+nums[x]≥y,那么位置 y 也可以到达。
步骤:我们依次遍历数组中的每一个位置,并实时维护最远可以到达的位置。对于当前遍历到的位置 x,如果它在 最远可以到达的位置 的范围内,那么我们就可以从起点通过若干次跳跃到达该位置,因此我们可以用 x+nums[x] 更新最远可以到达的位置。
如果 最远可以到达的位置 大于等于数组中的最后一个位置,那就说明最后一个位置可达,直接返回 True 。反之,如果在遍历结束后,最后一个位置仍然不可达,我们就返回 False 作为答案。
4.3、代码
class Solution:
def canJump(self, nums: List[int]) -> bool:
n , _max = len(nums) , 0
for i in range(n):
if i <= _max: #这一行判断一定要有,表示能到达,例如[3, 2, 1, 0, 4]就不能跨过第三位。
_max = max(_max,i+nums[i])
if _max >= n-1: #注意这里是n-1,比如[0]或者上面的题目。
return True
return False