贪心
贪心的思想就是通过局部的最优解得到全局的最优解。使用贪心的方法需要满足以下两个条件:
贪心选择性
贪心选择性指的是:一个优化问题的全局优化解可以通过局部优化选择得到。换句话说,当我们要解一个问题的时候,先做一步最优选择。这个问题被解决了一部分,然后再对剩下没解决的问题用迭代的方式继续使用上一步的方法做当前的最优选择。
举个更简单的例子,一个人吃不下一个蛋糕,但是他想把尽可能吃得越甜越好,那么他第一口就会选择最甜的那部分吃,这个最甜的部分就是最优选择。吃完了第一口,他再从剩下的部分中选择最甜的一口,直到他吃不下为止。每一口都是当前问题的最优选择,所以最后整个问题的解就是这所有的最优选择构成的。
最优子结构
最优子结构:一个优化问题的最优解包含它的子问题的最优解。
同样还是举一个相同的例子,一个人要吃蛋糕,但是他的胃口比上次更少了,但他仍然想吃最甜的,那么,他根据上次吃蛋糕的方法吃仍然是能吃到最甜的吃法。
用通俗的方式来理解这两个条件,就是说一个人能吃n口蛋糕,他想尽可能吃得最甜,那么他的做法就是每一口都吃当前最甜的那个部分;而验证他这种吃法正确的方法就是,如果他现在胃口小吃不了n口了,他用之前的吃法仍然能吃得尽可能最甜。
有时候,同一道题目既可以用贪心算法也可以用动态规划算法来求解。判别一道题目使用贪心还是用动态规划,可以用这个问题是否可以先做一步最优选择来判别;
- 如果原问题本身无法明确找到一个最优选择,那么就表示这个方法无法使用贪心,而应该使用动态规划根据子问题求解原问题。
- 如果原问题可以看出当前最优选择,那么就可以用贪心来解题。
- 贪心的时间复杂度一般比动态规划要低,有些题目虽然两种方法都能解,但可能其中一种方法会超时。做题的时候需要甄别。
1. 无重叠区间
链接:无重叠区间(中等)
这道题要找的是需要移除区间的最小数量,我们可以换一种解法,也就是求不重叠的最大区间数量,这种解法更适合解题。那么,这道题就有两种解法了。
第一种是动态规划的解法。我们可以用一个数组来保存以第 i 个区间结尾的不重叠的最大区间数量。为了让子问题得出原问题的解,我们应该让区间排序。那么问题来了,区间可能有重复段,如何排序区间,使得我们能够通过动态规划求出解呢?
用动态规划的思想,我们可以根据区间左侧的值来做排序,然后来分类讨论。使用一个数组dp来存放子问题的解。排序之后,对于某个区间 i ,我让任意0 < j < i 的区间都判断是否与区间 i 重合,如果没有重合,那么就可以沿用 j 的解得到dp[i]的解,即dp[i] = dp[j] + 1;如果有重合,那么不选择用这个区间更新。最后,我们选择最大的解作为解即可。判断两个区间是否重合,只要看 i 区间的左侧是否比 j 区间的右侧大即可。
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
//区间个数
int n = intervals.length;
//对左区间做排序
Arrays.sort(intervals, new Comparator<int[]>(){
@Override
public int compare(int[] a, int[] b){
return a[0] - b[0];
}
});
int[] dp = new int[n];
for(int i = 0; i<n; i++){
dp[i] = 1;
}
for(int i = 1; i<n; i++){
for(int j = i-1; j>=0; j--){
//如果没有区间重叠
if(intervals[i][0]>=intervals[j][1]){
dp[i] = Math.max(dp[i], dp[j] + 1);
}
}
}
return n-dp[n-1];
}
}
虽然这也是一种解决方法,但是这道题动态规划求解很可能会超出时间限制,此时我们可以使用另一种方法,即贪心算法。
我们使用贪心的时候,每次走一步最优选择,然后再将剩余问题取最优选择,最后得到解。那么,需要思考的是,当前问题的第一步最优解是什么?也就是说,我们需要找出不重叠的最大区间数中最左边的区间。
怎么得到答案中最左边的区间呢?其实这个问题就跟会议室安排问题是一个性质,每个区间占用一段时间的资源,安排第一个会议,其实就是要找最早结束的会议。因为只有最早结束会议,才能腾出更多的时间安排剩下的会议。与之对应的,我们就是要找区间右侧最小的区间。对区间右侧数进行排序后,我们在保证不重叠的情况下,选择右侧最小的区间。和第一步一样,这表示了当前最优选择。
class Solution {
public int eraseOverlapIntervals(int[][] intervals) {
//区间个数
int n = intervals.length;
//对左区间做排序
Arrays.sort(intervals, new Comparator<int[]>(){
@Override
public int compare(int[] a, int[] b){
return a[1] - b[1];
}
});
int right = intervals[0][1];
int ret = 1;
for(int i = 1; i<n; i++){
//前一个区间右侧小于等于后一个区间左侧
if(right<=intervals[i][0]){
ret++;
right = intervals[i][1];
}
}
return n-ret;
}
}