(31/60)贪心理论、分发饼干、摆动序列、最大子序和

贪心的一天,头好晕

理论基础

什么是贪心

每次选择都采取局部最优,最终得到全局最优。

(一定是每个阶段都采取局部最优,能够推出全局最优的,如果得不到全局最优就不用贪心法)

套路

没有套路。

但是可以判断用不用贪心:

通过数学归纳/反证法的方式,模拟一下看看能不能局部最优->整体最优。

(一般情况下就是反证法就行了,看看能不能找到反例,得不到整体最优的)

贪心法的一般过程

贪心算法一般分为如下四步:

  • 将问题分解为若干个子问题
  • 找出适合的贪心策略
  • 求解每一个子问题的最优解
  • 将局部最优解堆叠成全局最优解

这个四步其实过于理论化了,我们平时在做贪心类的题目 很难去按照这四步去思考,真是有点“鸡肋”。

做题的时候,只要想清楚 局部最优 是什么,如果推导出全局最优,其实就够了。


分发饼干

leetcode:455. 分发饼干

贪心法

思路

排序后,遍历每个孩子,找最小能满足胃口的饼干,最后结果就是尽可能多地满足孩子。

复杂度分析

时间复杂度:O(NlogN)。主要花在排序上。

空间复杂度:O(1)。

注意点

  1. 双指针就够了,不用起两层循环。

代码实现

双指针法(暴力法两层循环太辣眼睛了就不放上来了)

class Solution {
public:
// 胃口和饼干排序
// 遍历孩子,对每个孩子遍历饼干,找可能的最小的饼干
int findContentChildren(vector<int>& g, vector<int>& s) {
sort(g.begin(),g.end());
sort(s.begin(),s.end());
int count = 0;
// 不用套两个循环,可以双指针
int gIndex = 0;
int sIndex = 0;
while(gIndex != g.size() && sIndex != s.size()){
if(s[sIndex] >= g[gIndex]){ // 能满足 下一个孩子 下一块饼干
count++;
gIndex++;
sIndex++;
}else{ // 满足不了,还是这个孩子,下一块饼干
sIndex++;
}
}
return count;
}
};

摆动序列

leetcode:376. 摆动序列

回溯法

思路

理论上应该是可行的,但是我写不出来,在面对输入[1,2,3,4,5,6,7,8,9]时,不能正确返回[2]。

代码实现

class Solution {
private:
vector<vector<int>> result;
vector<int> path;
public:
// 试下回溯暴力枚举
void backtracking(vector<int>& nums,int begin,int sign){
if(path.size() > 0) result.push_back(path);
for(int i = begin;i < nums.size();i++){
// path元素不足两个时,直接放入元素
if(path.size() <= 1) path.push_back(nums[i]);
else{ // 两个元素及以上时,检查符号是否相反
if( (nums[i] - path.back()) * sign > 0){
path.push_back(nums[i]);
backtracking(nums,i + 1,-sign);
path.pop_back();
}
}
}
}
int wiggleMaxLength(vector<int>& nums) {
if(nums.size() <= 2) return nums.size();
int sign = 0;
if(nums[1] - nums[0] > 0 ) sign = 1;
else if(nums[1] - nums[0] < 0) sign = -1;
backtracking(nums,0,-sign);
int ret = 0;
for(vector<int> vec : result){
if(vec.size() > ret) ret = vec.size();
}
return ret;
}
};

贪心法

思路

出现峰值result++,且规定最右边有一个峰值。

可能的三种情况:

  1. 上下坡出现峰值 。preDiff > 0 && postDiff < 0 || preDiff < 0 && postDiff > 0
  2. 上下坡出现平坡。这种也要算,所以preDiff >= 0 && postDiff < 0 || preDiff <= 0 && postDiff > 0
  3. 单调上下出现平坡。这种不能算,preDiff只在出现峰值时更新即可

综上,条件就是preDiff >= 0 && postDiff < 0 || preDiff <= 0 && postDiff > 0,而且每次只计算postDiff,如果是峰值才更新preDiff

(本质上来说,就是把坡压缩掉)

复杂度分析

时间复杂度:O(N)。一轮遍历。

空间复杂度:O(1)。

注意点

  1. nums有0个、1个元素时直接返回大小,但2个及以上就要在循环里了(2个元素时要判断是否是相等的两个元素)。

代码实现

class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
if(nums.size() <= 1) return nums.size();
int preDiff = 0; // 前差值
int postDiff = 0; // 后差值
int result = 1; // 规定最右边有一个峰值
for(int i = 0;i < nums.size() - 1;i++ ){
postDiff = nums[i+1] - nums[i];
// 如果出现峰值,就result++并更新preDiff
// 这步压缩平坡,只有出现峰值才会进入操作
if( (preDiff >= 0 && postDiff < 0 ) || (preDiff <= 0 && postDiff > 0) ){
result++;
preDiff = postDiff;
}
}
return result;
}
};

最大子数组和

leetcode:53. 最大子数组和

贪心法

思路

局部最优:和为负数时,不论加甚麽都只会拖累整体累和。因此连续和为负数的时候,抛弃当前累和,从下一个元素开始计算累和。

整体最优:得到最大连续子数组的累和。

复杂度分析

时间复杂度:O(N)。遍历一轮。

空间复杂度:O(1)。

注意点

  1. 求最大和,要在遍历过程中尝试向上更新最大值(最大值可能出现在中间)。

代码实现

class Solution {
public:
// 连续和为负数时,从当前元素开始计数
int maxSubArray(vector<int>& nums) {
int sum = 0;
int maxSum = INT32_MIN;
for(int i = 0;i < nums.size();i++){
sum += nums[i];
maxSum = sum > maxSum ? sum : maxSum;
if(sum < 0){
sum = 0;
}
}
return maxSum;
}
};
posted @   Tazdingo  阅读(924)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示