贪心简单题
以下三道题目就是简单题,大家会发现贪心感觉就是常识。是的,如下三道题目,就是靠常识,但我都具体分析了局部最优是什么,全局最优是什么,贪心也要贪的有理有据!
455-分发饼干
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
示例 1:
输入: g = [1,2], s = [1,2,3]
输出: 2
解释:
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.
「这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩」。
这个例子可以看出饼干9只有喂给胃口为7的小孩,这样才是整体最优解。
/**
* 贪心策略,先将饼干数组和小孩数组排序。然后从后向前遍历小孩数组,用大饼干优先满足胃口大的,并统计满足小孩数量。
*/
class Solution {
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);
Arrays.sort(s);
int index = s.length - 1; // 饼干数组的下表
int result = 0;
for (int i = g.length - 1; i >= 0; i--) {
if (index >= 0 && s[index] >= g[i]) {
result++;
index--;
}
}
return result;
}
}
复杂度分析
时间复杂度:O(nlogn):快排O(nlogn),遍历O(n),加一起就是还是O(nlogn)。
空间复杂度:O(1)
1005-K次取反后最大化的数组和
给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)
示例 3:
输入:A = [2,-3,-1,5,-4], K = 2
输出:13
解释:选择索引 (1, 4) ,然后 A 变为 [2,3,-1,5,4]。
本题思路其实比较好想了,如何可以让 数组和 最大呢?
贪心的思路,局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。
局部最优可以推出全局最优。
那么如果将负数都转变为正数了,K依然大于0,此时的问题是一个有序正整数序列,如何转变K次正负,让 数组和 达到最大。
那么又是一个贪心:局部最优:只找数值最小的正整数进行反转,当前数值可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。
那么本题的解题步骤为:
- 第一步:将数组按照绝对值大小从大到小排序,「注意要按照绝对值的大小」
- 第二步:从前向后遍历,遇到负数将其变为正数,同时K–
- 第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
- 第四步:求和
/**
*贪心算法
* 第⼀步:将数组按照绝对值⼤⼩从⼤到⼩排序,注意要按照绝对值的⼤⼩
* 第⼆步:从前向后遍历,遇到负数将其变为正数,同时K--
* 第三步:如果K还⼤于0,那么反复转变数值最⼩的元素,将K⽤完
* 第四步:求和
*/
class Solution {
public int largestSumAfterKNegations(int[] A, int K) {
int sum = 0;
int n = A.length;
Integer[] AA = new Integer[n];
for(int i = 0; i < n; i++)
AA[i] = A[i]; //new Integer(A[i]);自动装箱
Arrays.sort(AA, (a,b)->{
//逆序排序
return Math.abs(b) - Math.abs(a);
});
for (int i = 0; i < n; i++) {
if (AA[i] < 0 && K > 0) {
AA[i] *= -1;
K--;
}
}
if (K != 0) {
AA[n - 1] = (K % 2 == 1) ? (-AA[n - 1]) : AA[n - 1];
}
for (int i = 0; i < n; i++) {
sum += AA[i];
}
return sum;
}
}
复杂度分析
时间复杂度:O(n)
空间复杂度:O(n)
860-柠檬水找零
在柠檬水摊上,每一杯柠檬水的售价为 5 美元。
顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。
每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。
注意,一开始你手头没有任何零钱。
如果你能给每位顾客正确找零,返回 true ,否则返回 false 。
示例 1:
输入:[5,5,5,10,20]
输出:true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。
思路:只需要维护三种金额的数量,5,10和20。
有如下三种情况:
- 情况一:账单是5,直接收下。
- 情况二:账单是10,消耗一个5,增加一个10
- 情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5
所以局部最优:遇到账单20,优先消耗美元10,完成本次找零。全局最优:完成全部账单的找零。
/**
* 贪心算法
* 有如下三种情况:
* - 情况一:账单是5,直接收下。
* - 情况二:账单是10,消耗一个5,增加一个10
* - 情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5
*/
class Solution {
public boolean lemonadeChange(int[] bills) {
int five = 0, ten = 0, twenty = 0;
for (int bill : bills){
//情况一:账单是5,直接收下。
if (bill == 5) five++;
//情况二:账单是10,消耗一个5,增加一个10
if (bill == 10){
if (five <= 0) return false;
ten++;
five--;
}
//情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5
if (bill == 20){
// 优先消耗10美元,因为5美元的找零用处更大,能多留着就多留着
if (five > 0 && ten > 0){
five--;
ten--;
twenty++; // 其实这行代码可以删了,因为记录20已经没有意义了,不会用20来找零
}else if (five >= 3){
five -= 3;
twenty++; // 同理,这行代码也可以删了
}else{
return false;
}
}
}
return true;
}
}
复杂度分析
时间复杂度:O(n),其中 n是 bills的长度
空间复杂度:O(1)
贪心中等题
376-摆动序列
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。输出: 6
「局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值」。
「整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列」。
实际操作上,其实连删除的操作都不用做,因为题目要求的是最长摆动子序列的长度,所以只需要统计数组的峰值数量就可以了(相当于是删除单一坡度上的节点,然后统计长度)这就是贪心所贪的地方,让峰值尽可能的保持峰值,然后删除单一坡度上的节点。
**注意:**当nums.length>1,则至少都会有1个数字可以当作是摆动序列,所以result的初始值为1。
class Solution {
public int wiggleMaxLength(int[] nums) {
if (nums.length <= 1) return nums.length;
int curDiff = 0; // 当前一对差值
int preDiff = 0; // 前一对差值
int result = 1; // 记录峰值个数,序列默认序列最右边有一个峰值,至少有1个数能保证是摆动序列
for (int i = 1; i < nums.length;i++){
curDiff = nums[i] - nums[i - 1];
if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)){
// 出现峰值
result+