正文
贪心算法
什么是贪心算法?
贪心算法的本质是选择每一阶段的局部最优,从而达到全局最优。
贪心算法的一般解题步骤:
贪心算法一般分为如下的四步:
- 将问题分解为若干个子问题
- 找出合适的贪心策略
- 求解每一个子问题的最优解
- 将局部最优解堆叠为全局的最优解
那么我们开始我们的刷题之路吧。
455. 分发饼干
思路分析:
其实是可以这么考虑的,目前是有两种思考的方式, 优先满足于小胃口的孩子还是优先于满足大胃口的孩子。
如果说我尝试的是从小胃口的开始满足的话, g[i]
胃口值都要小于等于每块饼干的尺寸s[i]
。
g[i] <= s[i]
所以我们需要对饼干和胃口的数组进行一次升序的排列
g.sort((a, b) => a - b)
s.sort((a, b) => a - b)
那么实际中我们去思考的点是:
- 谁去满足于谁
- 需要 s[i]去满足于 g[i] 从小到大的胃口
代码实现:
/**
* @param {number[]} g
* @param {number[]} s
* @return {number}
*/
var findContentChildren = function(g, s) {
g.sort((a, b) => a-b)
s.sort((a, b) => a-b)
let index = 0; // 返回满足目标的最大值
for (let i = 0; i < s.length; i++) {
if (g[index] < s[i]) {
index++;
}
}
return index;
}
376. 摆动序列
思路分析:
局部最优: 删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部的峰值
整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列
我们从局部最优推出到全局的最优,并举不出反例的话, 我们就尝试一下贪心的操作:
在我们实际的操作过程中, 其实连删除的操作都不用做,因为题目的要求是最长摆动子序列的长度,我们只需要统计数组峰值的数量就可以了。
大致会分为两种情况:
- preDiff <= 0 和 curDiff > 0
- preDiff >= 0 和 curDiff < 0
代码实现:
/**
* @param {number[]} nums
* @return {number}
*/
var wiggleMaxLength = function(nums) {
// base case
if (nums.length <= 1) return nums.length;
let result = 1;
let curDiff = 0; // 当前的一堆差值
let preDiff = 0; // 前一对差值
for (let i = 0; i < nums.length; i++) {
curDiff = nums[i+1] - nums[i];
// 如果当前差值和上一个差值为一正一负
// 等于0的情况表示 初始化的 preDiff
if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
result++;
// 更新前一个
preDiff = curDiff;
}
}
return result;
}
53. 最大子数组和
思路分析:
那么这道题如何贪心呢? 我们举个例子,如果 -2, 1 在一起计算起点的时候, 一定是从1开始的,为啥呢?因为负数肯定会拉低总和的, 这就是贪心贪的地方。
那么,如果做到局部最优呢?
当前的"连续和"为负数的时候就应该立刻放弃, 从下一个元素重新计算"连续和",因为负数加上下一个元素“连续和”只会越来越小的。
全局最优: 选取最大"连续和"
我们在局部最优的情况下, 并记录最大的"连续和",可以推出全局最优。
如果是从我们写代码的角度来说,遍历nums, 从头开始用count
累加和, 如果count
一旦加上nums[i]
变为负数了, 那么就应该从 nums[i]+ nums[i+1]
开始。因为变为负数的count
, 只会拖累总和。
那么如何调整区间的终止位置呢?如何保证能获得最大的"连续和"?
if (count > result) result = count
就相当于是用result
来记录最大子序列区间和(变相调整了终止的位置)
代码实现:
/**
* @param {number[]} nums
* @return {number}
*/
var maxSubArray = function(nums) {
// base case
let count = 0;
let reslut = -Infinity;
for (let i = 0; i < nums.length; i++) {
// 累加和
count += nums[i]
// 去区间的最大值
if (count > result) {
result = count;
}
// 其实这里的设计就是说先比较 count 和 result 的大小
// 可能在下一次的累加中 count < 0 我们直接返回上次的结果就可以了
// 相当于重置最大子序起始位置,因为遇到了负数一定是拉低了总和
if (count <= 0) count = 0;
}
return result;
}