难以理解的贪心算法
贪心算法不好解释,越是权威的语言解释就越难懂。用一句话来说就是“只可意会,不能言传”。而且贪心的题目没有固定的套路,一题一样,好在大部分贪心算法的题都不是特别难,因此贪心的学习方法就是“直接做题,不考虑贪不贪心”。
贪心算法就是在对问题进行求解时,在每一步选择中都采取最好的或者最优的选择,从而希望能够导致结果是最好的或者最优的算法;贪心算法所得到的结果不一定是最优解,但都是相对近似的最优解的结果。
贪心算法不能保证一定能得到最优解,但是对很多问题确实可以得到最优解。
贪心常见的经典应用场景如下:
1.排序问题:选择排序、拓扑排序
2.优先队列:堆排序
3.赫夫曼压缩编码
4.图里的Prim、Fruskal和Dijkstra算法
5.硬币找零问题
6.分数背包问题
7.并查集的按大小或者高度合并问题或者排名
8.任务调度部分场景
9.一些复杂问题的近似算法
贪心问题的举例
分发饼干
LeetCode455:
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。对每个孩子 i ,都有一个胃口值g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j ,都有一个尺寸s[j]。如果s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i,这个孩子会得到满足。你的目标尽可能满足越多数量的孩子,并输出这个最大数值。
示例1:
输入:g = [1,2,3] , s = [1,1]
输出:1
解释:你有三个孩子和两块小饼干,3个孩子的胃口值分别是:1,2,3。虽然你有两块小饼干,由于他们的尺寸都是1,你只能让胃口值是1的孩子满足。所以你应该输出1。
示例2:
输入:g = [1,2] , s = [1,2,3]
输出:2
解释:你有两个孩子和三块小饼干,2个孩子的胃口值分别是:1,2。你拥有的饼干数量和尺寸都足以让所有孩子满足,所以你应该输出2。
思路:
要满足小孩的胃口,扼要避免饼干浪费,可以将大饼干优先满足给胃口大的。
先将饼干数组和小孩数组排序。然后从后遍历小孩数组,用大饼干满足胃口大的,并且满足小孩数量就解决了。
public static int findContentChildren(int[] g,int[] s){
Arrays.sort(g);
Arrays.sort(s);
int count = 0;//用于计数
int start = s.length - 1;
for (int index = g.length - 1;index >= 0; index--){
if (start >=0 && g[index] <= s[start]){
start--;
count++;
}
}
return count;
}
柠檬水找零
LeetCode860:
在柠檬水摊上,每一杯柠檬水的售价为5美元,顾客排队购买你的产品,(按账单bills支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你支付5美元、10美元或者20美元。你必须给每个顾客正确的找零,也就是说净交易是每位顾客向你支付5美元。
注意,一开始你手头没有任何零钱。
给你一个整数数组bills,其中bills[i]是第i位顾客付的账。如果你能给每位顾客正确地找零,返回true,否则返回false。
示例1:
输入:bills = [5,5,5,10,20]
输出:true
解释:前3位顾客那里,我们按顺序收取3张5美元。第4位顾客那里,我们收取一张10美元的钞票,并返还5美元。第5位顾客那里,我们找还一张10美元的钞票和一张5美元的钞票。由于所有顾客都得到了正确的找零,所以我们输出true。
示例2:
输入:bills = [5,5,10,10,20]
输出:true
解释:前2位顾客那里,我们按顺序收取2张5美元。对于接下来的2位顾客,我们收取一张10美元的钞票,然后返还5美元。对于最后一位顾客,我们无法退回15美元,因为我们现在只有两张10美元的钞票。由于不是每位顾客都得到了正确的找零,所以答案是false。
思路:
当收取5美元时,直接手下即可。
当收取10美元时,需要找零5美元,如果没有5美元面值的钞票,则无法正确找零。
当收取20美元时,需要找零15美元,有两种找零方式:1.一张10美元一张5美元;2.三张5美元。如果两种方式都没有,则无法正确找零。当正确找零时,尽量按第一张方式找零,因为使用5美元的时候比使用10美元的时候多。
public static boolean lemonadeChange(int[] bills){
int five = 0;
int ten = 0;
for (int bill : bills){
if (bill == 5){
five++;
}else if (bill == 10){
//若无5元,直接返回false
if (five == 0){
return false;
}
five--;
ten++;
}else {
//5美元,10美元都有,优先扣除
if (five > 0 && ten > 0){
five--;
ten--;
}else if (five >= 3){
five -= 3;
}else {
return false;
}
}
}
return true;
}
分发糖果
LeetCode135:
n个孩子站成一排。给你一个整数数组ratings表示每个孩子的评分。
你需要按照以下要求,给这些孩子分发糖果:
每个孩子至少分配到1个糖果。
相邻两个孩子评分更高的孩子会获得更多的糖果。
请你给每个孩子分发糖果,计算并返回需要准备的最少糖果数目。
示例1:
输入:ratings = [1,0,2]
输出:5
解释:你可以分别给第一个、第二个孩子分发2、1、2颗糖果。
示例2:
输入:ratings = [1,2,2]
输出:4
解释:你可以分别给第一个、第二个、第三个孩子分发1、2、1颗糖果。第三个孩子只要得到1颗糖果,这满足题面中的两个条件。
思路:
每个孩子至少一个糖果,当后一个孩子比前一个孩子积分多时,糖果就加1,
public static int candy(int[] ratings){
int[] candyVec = new int[ratings.length];
candyVec[0] = 1;
for (int i = 1; i < ratings.length; i++){
if (ratings[i] > ratings[i-1]){
candyVec[i] = candyVec[i-1] + 1;
}else {
candyVec[i] = 1;
}
}
for (int i = ratings.length - 2; i >= 0; i--){
if (ratings[i] > ratings[i+1]){
candyVec[i] = Math.max(candyVec[i],candyVec[i+1] + 1);
}
}
int ans = 0;
for (int s : candyVec){
ans += s;
}
return ans;
}