(31/60)贪心理论、分发饼干、摆动序列、最大子序和
贪心的一天,头好晕
理论基础
什么是贪心
每次选择都采取局部最优,最终得到全局最优。
(一定是每个阶段都采取局部最优,能够推出全局最优的,如果得不到全局最优就不用贪心法)
套路
没有套路。
但是可以判断用不用贪心:
通过数学归纳/反证法的方式,模拟一下看看能不能局部最优->整体最优。
(一般情况下就是反证法就行了,看看能不能找到反例,得不到整体最优的)
贪心法的一般过程
贪心算法一般分为如下四步:
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠成全局最优解
这个四步其实过于理论化了,我们平时在做贪心类的题目 很难去按照这四步去思考,真是有点“鸡肋”。
做题的时候,只要想清楚 局部最优 是什么,如果推导出全局最优,其实就够了。
分发饼干
leetcode:455. 分发饼干
贪心法
思路
排序后,遍历每个孩子,找最小能满足胃口的饼干,最后结果就是尽可能多地满足孩子。
复杂度分析
时间复杂度:O(NlogN)。主要花在排序上。
空间复杂度:O(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++,且规定最右边有一个峰值。
可能的三种情况:
- 上下坡出现峰值 。
preDiff > 0 && postDiff < 0 || preDiff < 0 && postDiff > 0
- 上下坡出现平坡。这种也要算,所以
preDiff >= 0 && postDiff < 0 || preDiff <= 0 && postDiff > 0
- 单调上下出现平坡。这种不能算,
preDiff
只在出现峰值时更新即可
综上,条件就是preDiff >= 0 && postDiff < 0 || preDiff <= 0 && postDiff > 0
,而且每次只计算postDiff
,如果是峰值才更新preDiff
。
(本质上来说,就是把坡压缩掉)
复杂度分析
时间复杂度:O(N)。一轮遍历。
空间复杂度:O(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)。
注意点
- 求最大和,要在遍历过程中尝试向上更新最大值(最大值可能出现在中间)。
代码实现
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; } };
分类:
算法
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)