leetcode系列-贪心

图片

贪心简单题

以下三道题目就是简单题,大家会发现贪心感觉就是常识。是的,如下三道题目,就是靠常识,但我都具体分析了局部最优是什么,全局最优是什么,贪心也要贪的有理有据!

455-分发饼干

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值 g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

示例 1:
输入: g = [1,2], s = [1,2,3]
输出: 2
解释:
你有两个孩子和三块小饼干,2个孩子的胃口值分别是1,2。
你拥有的饼干数量和尺寸都足以让所有孩子满足。
所以你应该输出2.

「这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩」

图片

这个例子可以看出饼干9只有喂给胃口为7的小孩,这样才是整体最优解。

/**
 * 贪心策略,先将饼干数组和小孩数组排序。然后从后向前遍历小孩数组,用大饼干优先满足胃口大的,并统计满足小孩数量。
 */
class Solution {
   
    public int findContentChildren(int[] g, int[] s) {
   

        Arrays.sort(g);
        Arrays.sort(s);

        int index = s.length - 1; // 饼干数组的下表
        int result = 0;

        for (int i = g.length - 1; i >= 0; i--) {
   

            if (index >= 0 && s[index] >= g[i]) {
   
                result++;
                index--;
            }
        }
        return result;
    }

}

复杂度分析

时间复杂度:O(nlogn):快排O(nlogn),遍历O(n),加一起就是还是O(nlogn)。

空间复杂度:O(1)

1005-K次取反后最大化的数组和

给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)

示例 3:
输入:A = [2,-3,-1,5,-4], K = 2
输出:13
解释:选择索引 (1, 4) ,然后 A 变为 [2,3,-1,5,4]。

本题思路其实比较好想了,如何可以让 数组和 最大呢?

贪心的思路,局部最优:让绝对值大的负数变为正数,当前数值达到最大,整体最优:整个数组和达到最大。

局部最优可以推出全局最优。

那么如果将负数都转变为正数了,K依然大于0,此时的问题是一个有序正整数序列,如何转变K次正负,让 数组和 达到最大。

那么又是一个贪心:局部最优:只找数值最小的正整数进行反转,当前数值可以达到最大(例如正整数数组{5, 3, 1},反转1 得到-1 比 反转5得到的-5 大多了),全局最优:整个 数组和 达到最大。

那么本题的解题步骤为:

  • 第一步:将数组按照绝对值大小从大到小排序,「注意要按照绝对值的大小」
  • 第二步:从前向后遍历,遇到负数将其变为正数,同时K–
  • 第三步:如果K还大于0,那么反复转变数值最小的元素,将K用完
  • 第四步:求和
/**
 *贪心算法
 *  第⼀步:将数组按照绝对值⼤⼩从⼤到⼩排序,注意要按照绝对值的⼤⼩
 *  第⼆步:从前向后遍历,遇到负数将其变为正数,同时K--
 *  第三步:如果K还⼤于0,那么反复转变数值最⼩的元素,将K⽤完
 *  第四步:求和
 */
class Solution {
   
    public int largestSumAfterKNegations(int[] A, int K) {
   
        int sum = 0;
        int n = A.length;
        Integer[] AA = new Integer[n];

        for(int i = 0; i < n; i++)
            AA[i] = A[i];              //new Integer(A[i]);自动装箱
            Arrays.sort(AA, (a,b)->{
       //逆序排序
            return Math.abs(b) - Math.abs(a);
        });

        for (int i = 0; i < n; i++) {
   
            if (AA[i] < 0 && K > 0) {
   
                AA[i] *= -1;
                K--;
            }
        }

        if (K != 0) {
   
            AA[n - 1] = (K % 2 == 1) ? (-AA[n - 1]) : AA[n - 1];
        }

        for (int i = 0; i < n; i++) {
   
            sum += AA[i];
        }

        return sum;
    }
}

复杂度分析

时间复杂度:O(n)

空间复杂度:O(n)

860-柠檬水找零

在柠檬水摊上,每一杯柠檬水的售价为 5 美元。

顾客排队购买你的产品,(按账单 bills 支付的顺序)一次购买一杯。

每位顾客只买一杯柠檬水,然后向你付 5 美元、10 美元或 20 美元。你必须给每个顾客正确找零,也就是说净交易是每位顾客向你支付 5 美元。

注意,一开始你手头没有任何零钱。

如果你能给每位顾客正确找零,返回 true ,否则返回 false 。

示例 1:
输入:[5,5,5,10,20]
输出:true
解释:
前 3 位顾客那里,我们按顺序收取 3 张 5 美元的钞票。
第 4 位顾客那里,我们收取一张 10 美元的钞票,并返还 5 美元。
第 5 位顾客那里,我们找还一张 10 美元的钞票和一张 5 美元的钞票。
由于所有客户都得到了正确的找零,所以我们输出 true。

思路:只需要维护三种金额的数量,5,10和20。

有如下三种情况:

  • 情况一:账单是5,直接收下。
  • 情况二:账单是10,消耗一个5,增加一个10
  • 情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5

所以局部最优:遇到账单20,优先消耗美元10,完成本次找零。全局最优:完成全部账单的找零。

/**
 * 贪心算法
 * 有如下三种情况:
 * - 情况一:账单是5,直接收下。
 * - 情况二:账单是10,消耗一个5,增加一个10
 * - 情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5
 */
class Solution {
   
    public boolean lemonadeChange(int[] bills) {
   


        int five = 0, ten = 0, twenty = 0;

        for (int bill : bills){
   

            //情况一:账单是5,直接收下。
            if (bill == 5) five++;

            //情况二:账单是10,消耗一个5,增加一个10
            if (bill == 10){
   
                if (five <= 0) return false;
                ten++;
                five--;
            }

            //情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5
            if (bill == 20){
   

                // 优先消耗10美元,因为5美元的找零用处更大,能多留着就多留着
                if (five > 0 && ten > 0){
   
                    five--;
                    ten--;
                    twenty++;  // 其实这行代码可以删了,因为记录20已经没有意义了,不会用20来找零
                }else if (five >= 3){
   
                    five -= 3;
                    twenty++; // 同理,这行代码也可以删了
                }else{
   
                    return false;
                }
            }
        }

        return true;
    }
}

复杂度分析

时间复杂度:O(n),其中 n是 bills的长度

空间复杂度:O(1)

贪心中等题

376-摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。仅有一个元素或者含两个不等元素的序列也视作摆动序列。

给定一个整数序列,返回作为摆动序列的最长子序列的长度。通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。

例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。输出: 6

图片

「局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值」

「整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列」

​ 实际操作上,其实连删除的操作都不用做,因为题目要求的是最长摆动子序列的长度,所以只需要统计数组的峰值数量就可以了(相当于是删除单一坡度上的节点,然后统计长度)这就是贪心所贪的地方,让峰值尽可能的保持峰值,然后删除单一坡度上的节点。

**注意:**当nums.length>1,则至少都会有1个数字可以当作是摆动序列,所以result的初始值为1。

class Solution {
   
    public int wiggleMaxLength(int[] nums) {
   

        if (nums.length <= 1) return nums.length;

        int curDiff = 0; // 当前一对差值

        int preDiff = 0; // 前一对差值

        int result = 1;  // 记录峰值个数,序列默认序列最右边有一个峰值,至少有1个数能保证是摆动序列

        for (int i = 1; i < nums.length;i++){
   

            curDiff = nums[i] - nums[i - 1];

            if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)){
       // 出现峰值

                result+
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值