思想
贪心算法(Greedy Algorithm)在对问题求解时,不从整体最优上加以考虑,而是做出在当前看来最好的选择。贪心算法预设了一个前提,就是认为全局最优解可以由局部最优解推出。
贪心算法的最直接的特点——“贪婪”
把求解的问题分成子问题;
对每个子问题求解,都应用最优原则选择,得到子问题的局部最优解;
把子问题的解局部最优解合成原来求解问题的一个解。
贪心算法不断的贪心的做出当前最优的选择。贪心算法不追求最优解,只找到满意解。
贪心算法是分阶段执行的,每一个阶段都根据当前的情况来判断,应用最优原则选择,而不考虑后续的发展整体的情况。
根据这样的性质,要求贪心法解决的问题具有“无后效性”——当前的决策不会影响到后续的决策,如果问题前后勾连紧密的话,会造成求解过程十分混乱。
贪心算法常常用于组合优化问题,它的求解过程是多步判断的过程。
如果一个待求解的问题具有以上的特征,可以使用贪心算法解决。
贪心法与其他相似算法
贪心法和分治法、动态规划一样,都需要对问题分解,定义最优的子结构。但是,贪心法与其他方法最大的区别在于,贪心法每一步选择完之后,局部最优解就确定了,不再回溯复盘,每个步骤的最优解确定后,就不再修改,直到算法结束。因为不做回溯处理,贪心法只在很少情况下可以得到真正的最优解,比如最短路径问题、图的最小生成树问题。
应用场景
贪心算法不一定得到整体的最优解,但最终结果也接近最优解。一些情况下,我们不必过分要求使用贪心算法得到最优解,也没有必要严格推理证明,使用贪心算法是一种不错的选择。实际应用中许多问题可以使用贪心算法得到最优解。
贪心算法能求出最优解的条件
贪心选择性质
贪心选择性质(greedy choice property):全局最优解可以由一系列局部最优解推出。
贪心选择性质是贪心算法可行的第一个基本要素,也是贪心算法与动态规划算法的主要区别。
贪心选择是采用从顶向下、以迭代的方法做出相继选择,每做一次贪心选择就将所求问题简化为一个规模更小的子问题。
对于一个具体问题,要确定它是否具有贪心选择的性质,我们必须证明每一步所作的贪心选择最终能得到问题的最优解。用数学归纳法证明,通过每一步贪心选择,最终可得到问题的一个整体最优解。
最优子结构
最优子结构(optimal substructure):当问题的最优解可以由子问题的最优解构成,那么该问题就具备最优子结构性质。
问题是否具备最优子结构性质,是问题是否可用贪心算法或动态规划算法求解的关键特征。
在子结构性质上与动态规划的区别
贪心算法的每一次操作都对结果产生直接影响,而动态规划不是。
贪心算法对每个子问题的解决方案都做出选择,不能回退;
动态规划则会根据以前的选择结果对当前进行选择,有回退功能。
动态规划主要运用于二维或三维问题,而贪心一般是一维问题。
贪婪算法的条件
要判断一个问题是否可以通过贪心算法得到最优解,是一件比较难的事情。这需要复杂而严格的数学证明。因此,虽然贪心算法简单容易实现,并且效率很高,但是使用贪心算法之前,必须对问题本身进行深入而透彻地分析证明,以保证使用贪心算法得到最优解。
特点
贪心算法的最直接的特点——“贪婪”
优点
直观、易懂,实现简单。算法一旦做出决定,就不去检查前面计算过的值。
如果一个问题的最优解只能用蛮力法穷举得到,则贪心法不失为寻找问题近似最优解的一种较好的方法。
缺点
局限性
对于很多问题,不能保证解是最佳的。因为贪心算法总是从局部出发,并没从整体考虑。
贪心算法不能保证最后求得的解是最优的;
贪心算法只能求满足某些约束条件的可行解的范围。
跳跃问题
55. 跳跃游戏
给定一个非负整数数组nums,你最初位于数组的 第一个下标 。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
判断你是否能够到达最后一个下标。
输入:nums = [2,3,1,1,4]
输出:true
解释:可以先跳 1 步,从下标 0 到达下标 1, 然后再从下标 1 跳 3 步到达最后一个下标。
输入:nums = [3,2,1,0,4]
输出:false
解释:无论怎样,总会到达下标为 3 的位置。但该下标的最大跳跃长度是 0 , 所以永远不可能到达最后一个下标。
解:
class Solution {
public:
bool canJump(vector<int>& nums) {
vector<int> jump;
for(int i = 0 ; i< nums.size() ; ++i){
jump.push_back(i+ nums[i]);
}
int nindex = 0;
int max_jump = jump[0];
while(nindex < nums.size() && nindex <= max_jump){
if(max_jump < jump[nindex]){
max_jump = jump[nindex];
}
nindex ++ ;
}
if(nindex == nums.size())
return true;
else
return false;
}
};
class Solution {
public:
bool canJump(vector<int>& nums) {
int len = nums.size();
if (len <= 1)
return true;
int maxDis = nums[0];
for (int i = 1; i < len - 1; i++) {
if (i <= maxDis) {
maxDis = max(maxDis, nums[i]+i);
}
}
return maxDis >= len - 1;
}
};
2、跳跃游戏 II
给定一个正整数数组,你最初位于数组的第一个位置。
数组中的每个元素代表你在该位置可以跳跃的最大长度。
你的目标是使用最少的跳跃次数到达数组的最后一个位置。
示例 :
输入: [2,3,1,1,4]
输出: 2
解释: 跳到最后一个位置的最小跳跃数是 2。
从下标为 0 跳到下标为 1 的位置,跳 1 步,然后跳 3 步到达数组的最后一个位置。
说明:
假设你总是可以到达数组的最后一个位置。