文章目录
455.分发饼干
假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干(关键,显示条件说出来了,一个饼干只能一个孩子,这是隐性条件,没必要说出来)。
- 对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;
- 对于每块饼干 j,都有一个尺寸 s[j] 。
- 如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值(int)。
孩子数组g,饼干数组s
贪心问题注意两点:
第一,贪心问题就是资源分配给人的问题,这里核心,一个饼干不可以分给多个孩子(所以一定要避免浪费),同时一个孩子只能吃一个饼干
第二,从题目中提取出局部最优和全局最优
局部最优:大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个
全局最优:喂饱尽可能多的小孩
贪心问题中,题目答案需要的全局最优,但是解题思路只能给出局部最优的算法,所以,要保证这个局部最优的是可以满足的全局最优的(即不能举出反例),这里就是每个饼干(资源)被浪费的量最少
因为一个饼干只能分配给一个孩子,然后大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子,那么就应该优先满足胃口大的。
不这样就会危险:第一,大饼干给小胃口可以,但是小饼干给大胃口不一定能满足,存在危险。
这样是安全的(无法举出反例):如果饼干都可以满足的孩子胃口,那么给大胃口的孩子还是给小胃口的孩子是一样的,因为最后要得到的只是孩子的总数而已。
示例 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.
对于对两个数组排序,然后逐一比较值就好
为了了满足更多的小孩,就不要造成饼干尺寸的浪费。
可以尝试使用贪心策略,先将饼干数组和小孩数组排序。然后从后向前遍历小孩数组,用大饼干优先满足胃口大的,并统计满足小孩数量。
代码:
// 时间复杂度:O(nlogn)
// 空间复杂度:O(1)
class Solution {
public int findContentChildren(int[] g, int[] s) {
if (g == null && g.length == 0) return 0; // 没有孩子,就是0
Arrays.sort(g);
Arrays.sort(s);
int result = 0; // 一开始还没满足一个孩子,所以为result为零
int index = s.length-1; // 饼干也是从大的开始
for (int i = g.length-1; i >= 0; i--) { // 孩子从大到小开始,如果最大的饼干也不能满足某个孩子,就放弃这个孩子,将最大的饼干给第二胃口的孩子,还是满足的不了就再下一个
if (index>=0){
if (s[index]>=g[i]){ // 饼干满足孩子,那么计数器加一,然后饼干先前面移动,因为当前饼干已经被使用了
result++;
index--;
}
}
}
return result;
}
}
关键:从饼干的角度考虑,对孩子for循环,如果最大的饼干喂不饱最大的孩子,就放弃这个孩子,就放弃这个孩子,孩子i–不断执行,只有满足条件才执行饼干index–
从孩子的角度来考虑,就会优先考虑小胃口的孩子,小饼干不一定能满足孩子,所以小饼干做for循环,如果最小的饼干不能满足最小的孩子,就放弃这个饼干,i++不断执行,孩子作为if判断
代码如下:
class Solution {
public int findContentChildren(int[] g, int[] s) {
if (g == null && g.length == 0) return 0; // 没有孩子,就是0
Arrays.sort(g);
Arrays.sort(s);
int result = 0; // 一开始还没满足一个孩子,所以为result为零
int index = 0;
for (int i = 0; i < s.length; i++) { // 小饼干不一定能满足孩子胃口,所以饼干的i要不断循环
if (index < g.length) {
if (s[i] >= g[index]) { // 饼干大于孩子
result++;
index++; // 孩子拿到饼干,所以孩子移动了
}
}
}
return result;
}
}
不写两个循环,写两个循环就是 O(n^2) 的时间复杂度了。
核心1:从小到大遍历饼干,小饼干不一定能满足孩子胃口,所以饼干的i要不断循环
核心2:从大到小遍历孩子,如果最大的饼干也不能满足某个孩子,就放弃这个孩子,将最大的饼干给第二胃口的孩子,还是满足的不了就再下一个
376. 摆动序列
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
示例 1:
输入: [1,7,4,9,2,5]
输出: 6
解释: 整个序列均为摆动序列。
示例 2:
输入: [1,17,5,10,13,15,10,5,16,8]
输出: 7
解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。
示例 3:
输入: [1,2,3,4,5,6,7,8,9]
输出: 2
「局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值」。
「整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列」。
局部最优推出全局最优,并举不出反例,那么试试贪心!
class Solution {
public int wiggleMaxLength(int[] nums) {
if (nums.length<=1) return nums.length;
int preDiff=0;
int curDiff=0;
int result=1; // 长度为2,即使是两个数字相等,子序列长度也有一个
for (int i=1;i<nums.length;i++){
curDiff = nums[i]-nums[i-1];
// 核心:和数学上的单调性一下,大于左边且小于右边,或,小于左边且大于右边
if ((curDiff<0 && preDiff>=0) || (curDiff>0 && preDiff<=0) ){ // 长度为2,即使是两个数字不等,子序列长度为2
result++;
preDiff = curDiff;
}
}
return result;
}
}
摆动子序列的局部最优和全局最优:
核心:和数学上的单调性一下,大于左边且小于右边,或,小于左边且大于右边
问题:为什么preDiff可以用等于号?
53. 最大子序和
给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。
示例:
输入: [-2,1,-3,4,-1,2,1,-5,4]
输出: 6
解释: 连续子数组 [4,-1,2,1] 的和最大,为 6。
class Solution {
public int maxSubArray(int[] nums) {
// 从最大的整数开始
int result=Integer.MIN_VALUE; // 这里不要写0,如果nums中仅一个负数,这个负数可以不断小
int sum=0;
for (int i=0;i<nums.length;i++){ // [0,n-1]
sum = sum + nums[i];
if (sum > result){
result = sum; // 遇到更大的就更新
}
if (sum < 0 )sum=0; // 变成了负数就马上设置为0,因为sum的初始化就是从0开始的
}
return result;
}
}
最大序列和关键在于两个条件:
第一个条件,要取出最大序列和,所以 sum>result,就用result=sum;
第二个条件,因为sum初始化从0开始,所以当一段序列和变为负数了马上抛弃,继续将sum初始为0,从新开始技术,这就是局部最优和全局最优的联系。
即:只要目前sum还是正的,就可以继续累加上去,要是sum>result,就更新下result,但是一旦sum变成了负的,就要抛弃前面的子序列,重新开始。