同一贪心思路的两种求解方式|Java 刷题打卡

简介: 同一贪心思路的两种求解方式|Java 刷题打卡

网络异常,图片无法展示
|


题目描述



这是 LeetCode 上的 781. 森林中的兔子 ,难度为 中等


Tag : 「贪心算法」


森林中,每个兔子都有颜色。其中一些兔子(可能是全部)告诉你还有多少其他的兔子和自己有相同的颜色。我们将这些回答放在 answers 数组里。


返回森林中兔子的最少数量。


示例:


输入: answers = [1, 1, 2]
输出: 5
解释:
两只回答了 "1" 的兔子可能有相同的颜色,设为红色。
之后回答了 "2" 的兔子不会是红色,否则他们的回答会相互矛盾。
设回答了 "2" 的兔子为蓝色。
此外,森林中还应有另外 2 只蓝色兔子的回答没有包含在数组中。
因此森林中兔子的最少数量是 5: 3 只回答的和 2 只没有回答的。
输入: answers = [10, 10, 10]
输出: 11
输入: answers = []
输出: 0
复制代码


说明:


  • answers 的长度最大为1000。
  • answers[i] 是在 [0, 999] 范围内的整数。


基本分析



首先,兔子它不会说谎 (`・ω・´),因此我们可以得出以下结论:


  • 同一颜色的兔子回答的数值必然是一样的
  • 但回答同样数值的,不一定就是同颜色兔子


举个🌰,假如有 3 只白兔,每只白兔的回答必然都是 2(对应结论 1);但假如有兔子回答了数值 2,可能只是三只白兔,也可能是三只白兔和三只灰兔同时进行了回答(对应结论 2)。


答案要我们求最少的兔子数量。


不妨设有某种颜色的兔子 mm 只,它们回答的答案数值为 cntcnt,那么 mmcntcnt 满足什么关系?


显然两者关系为 m = cnt + 1m=cnt+1


但如果是在 answersanswers 数组里,回答 cntcnt 的数量为 tt 的话呢?这时候我们需要分情况讨论:


  • t \leqslant cnt + 1tcnt+1 : 为达到「最少的兔子数量」,我们可以假定这 tt 只兔子为同一颜色,这样能够满足题意,同时不会导致「额外」兔子数量增加(颜色数量最少)。
  • t > cnt + 1t>cnt+1 : 我们知道回答 cntcnt 的兔子应该有 cnt + 1cnt+1 只。这时候说明有数量相同的不同颜色的兔子进行了回答。为达到「最少的兔子数量」,我们应当将 tt 分为若干种颜色,并尽可能让某一种颜色的兔子为 cnt + 1cnt+1 只,这样能够满足题意,同时不会导致「额外」兔子数量增加(颜色数量最少)。


换句话说,我们应该让「同一颜色的兔子数量」尽量多,从而实现「总的兔子数量」最少。


证明



我们来证明一下,为什么这样的贪心思路是对的:


基于上述分析,我们其实是在处理 answersanswers 数组中每一个 cntcnt ,使得满足题意的前提下,数值 cntcnt 带来的影响(总的兔子数量,或者说总的颜色数量)最小。


首先 answersanswers 中的每个数都会导致我们总的兔子数量增加,因此我们应该「让 answersanswers 的数字尽可能变少」,也就是我们需要去抵消掉 answersanswers 中的一些数字。


对于 answersanswers 中的某个 cntcnt 而言(注意是某个 cntcnt,含义为一只兔子的回答),必然代表了有 cnt + 1cnt+1 只兔子,同时也代表了数值 cntcnt 最多在 answersanswers 数组中出现 cnt + 1cnt+1 次(与其颜色相同的兔子都进行了回答)。


这时候我们可以从数组中移走 cnt + 1cnt+1 个数值 cntcnt(如果有的话)。


当每次处理 cntcnt 的时候,我们都执行这样的抵消操作。最后得到的 answersanswers 数值数量必然最少(而固定),抵消完成后的 answersanswers 中的每个 cntcnt 对答案的影响也固定(增加 cnt + 1cnt+1),从而实现「总的兔子数量」最少。


相反,如果不执行这样的操作的话,得到的 answersanswers 数值数量必然会更多,「总的兔子数量」也必然会更多,也必然不会比这样做法更优。


模拟解法



按照上述思路,我们可以先对 answersanswers 进行排序,然后根据遍历到某个 cntcnt 时,将其对答案的影响应用到 ansans 中(ans += cnt + 1),并将后面的 cntcntcntcnt 进行忽略。


代码:


class Solution {
    public int numRabbits(int[] cs) {
        Arrays.sort(cs);
        int n = cs.length;
        int ans = 0;
        for (int i = 0; i < n; i++) {
            int cnt = cs[i];
            ans += cnt + 1;
            // 跳过「数值 cnt」后面的 cnt 个「数值 cnt」 
            int k = cnt;
            while (k-- > 0 && i + 1 < n && cs[i] == cs[i + 1]) i++;
        }
        return ans;
    }
}
复制代码


  • 时间复杂度:O(n\log{n})O(nlogn)
  • 空间复杂度:O(1)O(1)


统计分配



我们也可以先对所有出现过的数字进行统计,然后再对数值进行(颜色)分配。


代码:


class Solution {
    int N = 1009;
    int[] counts = new int[N];
    public int numRabbits(int[] cs) {
        // counts[x] = cnt 代表在 cs 中数值 x 的数量为 cnt
        for (int i : cs) counts[i]++;
        int ans = counts[0];
        for (int i = 1; i < N; i++) {
            int per = i + 1;
            int cnt = counts[i];
            int k = cnt / per;
            if (k * per < cnt) k++;
            ans += k * per;
        }
        return ans;
    }
}
复制代码


  • 时间复杂度:O(n)O(n)
  • 空间复杂度:O(n)O(n)


拓展



保持题目现有的条件不变,假定颜色相同的兔子至少有一只发声,问题改为「问兔子颜色数量可能有多少种」,又该如何求解呢?


最后



这是我们「刷穿 LeetCode」系列文章的第 No.781 篇,系列开始于 2021/01/01,截止于起始日 LeetCode 上共有 1916 道题目,部分是有锁题,我们将先将所有不带锁的题目刷完。


在这个系列文章里面,除了讲解解题思路以外,还会尽可能给出最为简洁的代码。如果涉及通解还会相应的代码模板。


为了方便各位同学能够电脑上进行调试和提交代码,我建立了相关的仓库:github.com/SharingSour…


在仓库地址里,你可以看到系列文章的题解链接、系列文章的相应代码、LeetCode 原题链接和其他优选题解。

相关文章
|
5月前
|
Java
2022蓝桥杯大赛软件类省赛Java大学B组真题 刷题统计
2022蓝桥杯大赛软件类省赛Java大学B组真题 刷题统计
51 0
|
6月前
|
算法 Java C++
【Java 刷题记录】位运算
【Java 刷题记录】位运算
53 2
|
6月前
|
算法 Java
Java刷题有感
Java刷题有感
|
6月前
|
Java
JAVA数据结构刷题 -- 二叉树进阶
JAVA数据结构刷题 -- 二叉树进阶
43 0
|
6月前
|
存储 Java
JAVA数据结构刷题 -- 力扣二叉树
JAVA数据结构刷题 -- 力扣二叉树
54 0
|
6月前
|
算法 Java C++
刷题两个月,从入门到字节跳动offer丨GitHub标星16k+,美团Java面试题
刷题两个月,从入门到字节跳动offer丨GitHub标星16k+,美团Java面试题
|
6月前
|
消息中间件 前端开发 Java
java面试刷题软件kafka和mq的区别面试
java面试刷题软件kafka和mq的区别面试
|
存储 算法 Java
《代码随想录》刷题笔记——哈希表篇【java实现】
《代码随想录》刷题笔记——哈希表篇【java实现】
79 0
|
6月前
|
Java 索引
JAVA刷题之数组的总结和思路分享
JAVA刷题之数组的总结和思路分享
|
6月前
|
Java
JAVA刷题之字符串的一些个人思路
JAVA刷题之字符串的一些个人思路