一、什么是贪心呢
1、定义
如果找出局部最优并可以推出全局最优,就是贪⼼,如果局部最优都没找出来,就不是贪⼼。其实贪心可以和动态规划对比来记忆。动态规划也是局部最优推出全局最优,但是动态规划中的局部最优是可以影响你后面的情况的,而贪心则不会影响。比如下图要求计算从顶点0到顶点6的最短路径。此时就不能用贪心了,因为你第一步的选择会影响以后的选择。
2、贪心的解题步骤
贪⼼算法⼀般分为如下四步:
(1)将大问题分解为⼦问题
(2)找出适合的贪⼼策略
(3)求解每⼀个⼦问题的最优解
(4)将局部最优解堆叠成全局最优解
二、贪心的金典题目(简单、中等、困难)
1、简单题目
(1)假设你是⼀位很棒的家⻓,想要给你的孩⼦们⼀些⼩饼⼲。但是,每个孩⼦最多只能给⼀块饼⼲。对每个孩⼦ i,都有⼀个胃⼝值 g[i],这是能让孩⼦们满⾜胃⼝的饼⼲的最⼩尺⼨;并且每块饼⼲ j,都有⼀个尺⼨ s[j] 。如果 s[j] >= g[i],我们可以将这个饼⼲ j 分配给孩⼦ i ,这个孩⼦会得到满⾜。你的⽬标是尽可能满⾜越多数量的孩⼦,并输出这个最⼤数值。
1 <= g.length <= 3 * 104
0 <= s.length <= 3 * 104
1 <= g[i], s[j] <= 231 - 1
题⽬链接:https://leetcode-cn.com/problems/assign-cookies/
思路:将大问题分解为小问题,假设我现在就只有二个孩子,三块饼干。找出适合的贪⼼策略,我要想满足尽量多的孩子,我可以优先把小的的饼干给胃口小的孩子。求解每⼀个⼦问题的最优解,此时我就可以求出只有二个孩子,三块饼干的最优解。将局部最优解堆叠成全局最优解,回到题目我们可以将这个大问题分解n个上面的小问题,然后思考一下将这n个问题合并后,是不是全局最优。
Java代码实现:
class Solution {
public int findContentChildren(int[] g, int[] s) {
Arrays.sort(g);//把孩子胃口从小到大排序
Arrays.sort(s);//把饼干从小到大排序
int result=0;
for(int i=0;i<s.length;i++){
if(result<g.length&&s[i]>=g[result]){
result++;
}
}
return result;
}
}
2、中等题目
(1)摆动序列:如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第⼀个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。例如, [1,7,4,9,2,5] 是⼀个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反,[1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第⼀个序列是因为它的前两个差值都是正数,第⼆个序列是因为它的最后⼀个差值为零。给定⼀个整数序列,返回作为摆动序列的最⻓⼦序列的⻓度。 通过从原始序列中删除⼀些(也可以不删除)元素来获得⼦序列,剩下的元素保持其原始顺序。
题⽬链接:https://leetcode-cn.com/problems/wiggle-subsequence/
思路:将大问题分解为小问题,假设我现在就求的是[1,7,2,1]。找出适合的贪⼼策略,我想要删除⼀些(也可以不删除)元素来获得⼦序列,使剩下的元素最长。求解每⼀个⼦问题的最优解,此时我观察上面的数组可以知道,要使满足数组相邻元素上下摆动,当我们遇到单调的2个以上元素时,我们应该保留峰值的那个元素,以此使后面的元素可以有更多的选择。将局部最优解堆叠成全局最优解,回到题目我们可以将这个大问题分解n个上面的小问题,然后思考一下将这n个问题合并后,是不是全局最优。
Java代码:
class Solution {
public int wiggleMaxLength(int[] nums) {
if(nums.length==1){return 1;}
int pre=0,cur=0;
int result=1;
for(int i=1;i<nums.length;i++){
cur=nums[i]-nums[i-1];
if((pre>=0&&cur<0)||(pre<=0&&cur>0)){
result++;
pre=cur;
}
}
return result;
}
}
(2) 分发糖果
⽼师想给孩⼦们分发糖果,有 N 个孩⼦站成了⼀条直线,⽼师会根据每个孩⼦的表现,预先给他们评分。你需要按照以下要求,帮助⽼师给这些孩⼦分发糖果:
每个孩⼦⾄少分配到 1 个糖果。
相邻的孩⼦中,评分⾼的孩⼦必须获得更多的糖果。
那么这样下来,⽼师⾄少需要准备多少颗糖果呢?
链接:https://leetcode-cn.com/problems/candy/
思路:将大问题分解为小问题,假设我现在就3个孩子。找出适合的贪⼼策略,这题的贪心策略有二点,即要使用二次贪心策略,因为本题要考虑左右二个方向,当我们一起考虑时,这是很难的,所以我们先考虑从左向右,当右边的孩子大于左边的时将右边的孩子的糖果在左边的基础上加1,当小于时,就把右边的孩子的糖果变为1,第二次就是从右向左遍历,当遇到左边的大于右边的时,我们就要考虑是拿canducount[j+1]+1还是canducount[j],当然是拿最大的,因为这样才满足这二次的要求。将局部最优解堆叠成全局最优解,回到题目我们可以将这个大问题分解n个上面的小问题,然后思考一下将这n个问题合并后,是不是全局最优。
Java代码:
class Solution {
public int candy(int[] ratings) {
int[] canducount=new int[ratings.length];
Arrays.fill(canducount,1);//将糖果数组的每个地方都初始化为1,方便后续的操作
//从左向右
for(int i=1;i<ratings.length;i++){
if(ratings[i]>ratings[i-1]){
canducount[i]=canducount[i-1]+1;
}
}
//从右向左
for(int j=ratings.length-2;j>=0;j--){
if(ratings[j]>ratings[j+1]){
canducount[j]=Math.max(canducount[j+1]+1,canducount[j]);
}
}
int sum=0;
for(int i:canducount){
sum+=i;
}
return sum;
}
}
3、困难题目
(1)监控⼆叉树
给定⼀个⼆叉树,我们在树的节点上安装摄像头。节点上的每个摄影头都可以监视其⽗对象、⾃身及其直接⼦对象。计算监控树的所有节点所需的最⼩摄像头数量。
题⽬地址 : https://leetcode-cn.com/problems/binary-tree-cameras/
思路:首先明确这题是在二叉树上进行的算法,看到二叉树,就要想到二叉树的遍历,前中后序遍历,那这题选择哪一种方式呢?这还不知道,要看后续的分析,分析题目可知,题目要我们用最少的摄像头来监控整棵树,我们初始可以将摄像头放在叶子节点、头节点、有分支的节点,这三种上,如果放在叶子节点上那么就会使摄像头(可以监控上中下三层)损失了下层的监控使用,同理,放在头节点上也不好,所以放在有分支的节点上。本题贪心贪得地方是上面的黑体字。通过上面的分析我们可以知道刚刚开始放摄像头时要放在有分子的节点上,那是从头节点下的有分子的开始,还是从叶子节点上的分支节点开始放呢,从头下的分支开始,就有可能会放到叶子节点上去了,从叶子节点的上分支开始放,就有可能放到头节点上去了。放到头节点做多也就放一个,而如果放到叶子节点,那可不止一个了,就可能使指数级别的增长了。所以从下开始往上放,所以我们回答上面的问题,要选择后序,因为后序是左右跟的遍历顺序,是从下往上遍历的。以上分析确定了遍历的方式。那再来确定后续遍历的内部逻辑,当我们在后续遍历的内部处理每个节点时,我们怎么确定当前节点是不是要安装摄像头呢?我们一定要通过当前节点的左右子节点来确定要不要。在这颗树中,每个节点都有三种状态:该节点⽆覆盖、本节点有摄像头、本节点有覆盖。我们分别用三个数字来表示:0:该节点⽆覆盖1:本节点有摄像头2:本节点有覆盖。那问题来了,我们在代码内部怎么获取到当前左右节点的状态呢,因为后序遍历是递归遍历,所以我们可以通过后续递归遍历的返回值(即返回0、1、2)来确定当期节点的左右子节点的状态。
Java代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode() {}
* TreeNode(int val) { this.val = val; }
* TreeNode(int val, TreeNode left, TreeNode right) {
* this.val = val;
* this.left = left;
* this.right = right;
* }
* }
*/
class Solution {
public int result;//定义一个全局变量,记录结果
public int minCameraCover(TreeNode root) {
if(digui(root)==0){
result++;
}
return result;
}
public int digui(TreeNode root){
if(root==null){
return 2;
}
int left=digui(root.left);
int right=digui(root.right);
if(left==2&&right==2){
return 0;
}else if(left==0||right==0){
result++;
return 1;
}else{
return 2;
}
}
}