贪心

贪心

​ 和人的贪心一样,贪心算法就是让每一步总是按照某种指标选取最优。所以不是所有的贪心都能获得最后的答案,在我们使用贪心的时候,确保自己贪心算法的正确性。

​ 在我们无计可施的时候,也可以想一下贪心的算法,获得部分的分数。

常见套路

  • 按照某种规则排序,然后按照某种顺序选择

例题选讲

题意:

你是一个老板,想要凑够 n 元,现在有 1元,5元,10元的纸票,问最少用多少张纸票才能凑够n元。

示例:

输入: n = 11
输出: 2
解释:一张 10 元, 一张 1 元 就可以组成11元。

思路:

有 1元的存在,所以必定可以凑出来。

接下来就是如何减少纸票的数量了。

很明显的一个想法就是优先使用面值大的纸票,如果面值大的纸票不能用了,就换面值小的纸票。

public int solve(int n){
    int ans = 0;
    ans = n / 10;  // 算出来可以由几张面值为10元的纸票组成。
    n = n % 10;    // 还剩下多少钱。
    ans += n / 5;  // 答案加上可以由几张面值为5元的纸票组成的数量
    n = n % 5;     // 还剩下多少钱
    ans += n;      // 答案加上可以由几张面值为1元的纸票组成的数量
    return ans;    // 返回答案
}

题意:

455. 分发饼干

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

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

示例:

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

思路:

对于每一个孩子来说,如果有好几个大于他胃口的饼干,应该选哪一个饼干呢,

再想一想,就会发现要选择大于他胃口的饼干里面尺寸最小的那一个,这样尺寸大的就可以满足胃口大的孩子了。

这就是一个贪心的思想。

那么具体怎么实现这个算法呢,

对于每一个孩子,我都需要遍历一遍饼干数组,在剩余的饼干中找一个尺寸最小的且大于该孩子胃口的,

如果可以找到,答案就加一,该饼干的尺寸赋值为 0, 代表已经用过了。

所以个这个算法的时间复杂度是 O ( n 2 ) O(n^2) O(n2) 假设 n 为数组长度大的那一个。

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        int ans = 0;
        int mn = Integer.MAX_VALUE;

        for (int val : g){   // 循环孩子数组
            int tmp = mn,pos = -1;  // 初始化饼干大小, 饼干的位置。
            for (int i = 0; i < s.length; ++i){  // 循环饼干数组
                if (s[i] >= val){   // 饼干尺寸大于孩子胃口
                    if (s[i] <= tmp) {  // 找 饼干尺寸最小的
                        tmp = s[i];   // 记录饼干尺寸
                        pos = i;    // 记录饼干的位置
                    }
                }
            }
            if (pos != -1){   // 代表找到了对于的饼干位置
                ans++;      // 答案 加一
                s[pos] = 0;   // 饼干的尺寸变成 0, 代表不用再用了。
            }
        }
        return ans;
    }
}

到这里就可以解决问题了,我们还可以用更短的时间复杂度嘛?

考虑到孩子的位置,饼干的位置是没有影响的,所以我们可以进行排序。

如果我们把 s 数组, g 数组排序之后再考虑这个问题!!!

按照胃口从小到大的顺序枚举每个孩子,

对于第 i 个孩子,我从小到大枚举饼干尺寸,找到第 j 个位置的饼干正好大于该孩子的胃口,我就给第 i 个孩子匹配第 j 个饼干。

对于第 i + 1 个孩子, j 之前的饼干尺寸都不满足条件,j 也被用过了,所以我只要从 j + 1 开始枚举就可以了。

每个孩子只会被枚举一次, 每个饼干也只会被枚举一次,加上排序的时间复杂度。

所以总的时间复杂度就是 O ( n l o g 2 n ) O(nlog_2n) O(nlog2n),(这里省略了,具体的复杂度大家可以算一下)

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        int ans = 0;   // 最终答案
        int index = 0; // 用来枚举饼干的位置
        Arrays.sort(g);
        Arrays.sort(s);
        for(int i = 0; i < g.length; ++i){
            while(index < s.length && s[index] < g[i]) index++; // 找第一个大于等于胃口的饼干位置。
            if (index < s.length){  // 说明找到了对应的饼干
                ans++;
                index++;  // 这个位置饼干被用了, 位置加一
            }
        }
        return ans;
    }
}

题意:

1497. 检查数组对是否可以被 k 整除

给你一个整数数组 arr 和一个整数 k ,其中数组长度是偶数,值为 n

现在需要把数组恰好分成 n / 2 对,以使每对数字的和都能够被 k 整除。

如果存在这样的分法,请返回 True ;否则,返回 False

示例:

输入:arr = [-1,1,-2,2,-3,3,-4,4], k = 3
输出:true

思路:

  • 如果选 -1 另外一个数可以选择 1 4 7 , 这些数有哪些性质呢? 它都是 3 的倍数 +1, 我们把所有的数都映射到 0 ~ n-1, 具体选择哪一个就不重要了。
  • 把所有的数 %k,会得到一个数,但是这个数在 -k+1 ~ k-1 之间,再次把这个数 +k % k, 就可以变成 0 ~ k-1了。
  • 统计每个数出现的次数。对于 i 这个数, 想要组成 k, 另外一个数必然是 k-i ,所以只要比较一下两个数的个数是不是一样就可以了。
  • 最后特判 0 出现的次数是不是偶数。因为 0 只能和 0 组合在一起。
public boolean canArrange(int[] arr, int k) {
    int[] c = new int[k+10];
    for (int i = 0; i < arr.length; ++i) {
        arr[i] = (arr[i] % k + k) % k;
        c[arr[i]]++;  // 统计每个数出现的次数。
    }
    boolean vis = true;
    if ((c[0] & 1) == 1) vis = false;  // 特判 0 的个数
    for (int i = 1; i < k; ++i)
        if (c[i] != c[k-i]) vis = false;  // 判断两个数的个数是不是一样的。

    return vis;
}

训练题目:

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值