贪心算法
贪心算法的本质是选择每一段的局部最优,从而达到全局最优。
贪心算法的解题步骤:
- 将问题分解为若干个子问题
- 找出适合的贪心策略
- 求解每一个子问题的最优解
- 将全局最优解堆叠成全局最优解
455.分发饼干
题目: 假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。
对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。
链接: https://leetcode.cn/problems/assign-cookies
思路:
局部最优:大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个
全局最优:喂饱尽可能多的小孩
大饼干喂饱胃口大的小孩
将饼干数组和小孩数组排序,然后从后向前遍历小孩数组,用大饼干优先满足胃口大的,并统计满足小孩的数量。
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
int res = 0;
sort(g.begin(), g.end());
sort(s.begin(), s.end());
int j = s.size() - 1;
for(int i = g.size() - 1; i >= 0; --i){
if(j >= 0 && s[j] >= g[i]){
j--;
res++;
}
}
return res;
}
};
其他思路:小饼干喂饱胃口小的小孩
class Solution {
public:
int findContentChildren(vector<int>& g, vector<int>& s) {
int res = 0;
sort(g.begin(), g.end());//小孩的胃口值
sort(s.begin(), s.end());//饼干尺寸
int j = 0;
for(int i = 0; i < s.size(); ++i){ //遍历饼干
if(j < g.size() && s[i] >= g[j]){
res++;
j++;
}
}
return res;
}
};
376.摆动序列
题目: 如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。
相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。
链接: https://leetcode.cn/problems/wiggle-subsequence
思路:
- 定义
preDiff
记录前一个元素和当前元素的差值;
curDiff
记录当前元素和后一个元素的差值;
res
记录峰值个数。 - 满足条件:
preDiff < 0 && curDiff > 0
或preDiff > 0 && curDiff < 0
记一次波动。 - 其他情况:
①上下坡中有平坡:1 2 2 2 2 1,差值 1 0 0 0 -1
②数组首尾两端:2 2 5,差值 0 3
此时存在preDiff = 0
表示在数组的起点或者起点是平坡,此时只要curDiff
为正或负就可以记一次波动。
③单调坡中有平坡:1 2 2 2 3 4,差值 1 0 0 1 1
只在发生满足条件的”摆动变化“时,更新preDiff
的值,这样preDiff
在单调区间有平坡的时候就不会发生变化,造成误判。
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
if(nums.size() <= 1){
return nums.size();
}
int res = 1;
int preDiff = 0;
int curDiff = 0;
for(int i = 0; i < nums.size() - 1; ++i){
curDiff = nums[i] - nums[i + 1];
if((preDiff >= 0 && curDiff < 0) || (preDiff <= 0 && curDiff > 0)){
preDiff = curDiff;
res++;
}
}
return res;
}
};
细节问题:
- 如果数组中只有一个元素,摆动序列的长度为1。
- 如果数组一直递增,摆动序列的长度为2。
- 如果数组发生一次波动,摆动序列的长度为3。
所以,res
的数值应该是从1开始,遇到一次差值波动就+1
。
53.最大子数组和
题目: 给你一个整数数组 nums
,请你找出一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。子数组 是数组中的一个连续部分。
链接: https://leetcode.cn/problems/maximum-subarray/
思路:
【贪心解法】 如果-2,1
在一起时,计算起点时,一定是从1
开始计算,因为负数只会拉低总和,这就是贪心贪的地方。
- 局部最优: 当前”连续和“为负数的时候立刻放弃,从下一个元素重新计算”连续和“,因为负数加上下一个元素”连续和“只会越来越小。
- 全局最优: 选取最大”连续和“
局部最优的情况下,并记录最大的”连续和“,可以推出全局最优。
遍历nums
,从头开始用count
累积,如果count
一旦加上nums[i]
变为负数,那么就应该从nums[i+1]
开始从0
累积count
了,因为已经变为负数的count
,只会拖累总和。
每次贪心取count
为正数的时候,开始一个区间的统计。
class Solution {
public:
int maxSubArray(vector<int>& nums) {
int res = INT_MIN;
int count = 0;
for(int i = 0; i < nums.size(); ++i){
count += nums[i];
if(count >= res){ //***
res = count;
}
if(count <= 0){
count = 0;
}
}
return res;
}
};
时间复杂度:O(N)
空间复杂度:O(1)
注:***
处如果写为
if(count >= 0){
res = max(res, count);
}
当数列都为负数的时候,res
始终未被赋值。
【动态规划】
动态规划五部曲:
- 确定
dp
数组以及下标的含义
dp[i]
:包括下标i
(以下标为i
的元素为结尾)的最大连续子序列的和为dp[i]
- 确定递推公式
dp[i]
只有两个方向可以推出来:
①dp[i - 1] + nums[i]
,即nums[i]
加入当前连续子序列和
②nums[i]
,即从头开始计算当前连续子序列的和
dp[i] = max(dp[i - 1] + nums[i], nums[i]);
dp
数组如何初始化
从递推公式中可以看出dp[i]
的值依赖于dp[i - 1]
的状态,dp[0]
就是递推公式的基础。
dp[0] = nums[0]
- 确定遍历顺序
从前向后遍历 - 举例推导
dp
数组
class Solution {
public:
int maxSubArray(vector<int>& nums) {
vector<int> dp(nums.size());
dp[0] = nums[0];
int res = nums[0];
for(int i = 1; i < nums.size(); ++i){
dp[i] = max(dp[i - 1] + nums[i], nums[i]);
res = max(res, dp[i]);
}
return res;
}
};