贪心猫的鱼干大分配
题目描述
在猫星球上,小R负责给一行排队的猫分发鱼干。每只猫有一个等级,等级越高的猫应该得到更多的鱼干。规则如下:
- 每只猫至少得到一斤鱼干。
- 如果一只猫的等级高于它相邻的猫,它就应该得到比相邻的猫更多的鱼干。
小R想知道,为了公平地满足所有猫的等级差异,他至少需要准备多少斤鱼干。
题目分析
正如题目所示,这是一道典型的贪心算法题。要求每只猫至少获得一条鱼干,如果相邻的猫等级高就应该得到更多鱼干。这种类似的题目很多,都是换汤不换药,一旦做出一道该类型题目,这类题就算是掌握了。但是这种类型的题目思路还是有点难想的,接下来我将带着读者一同找出本体破冰点。
我们从题目知道问的是至少准备多少斤鱼干,所以就是求最少鱼干,那么在遵守题目规则的情况下,相邻小猫获得鱼干期望最小差值应为1。我们先考虑递增的情况,即如下:
小猫等级:【1,3,4,6,12】
小猫鱼干:【1,2,3,4,5】
在这种情况下可以用最少鱼干完成分配。现在让我们加入特殊情况
情况1:
小猫等级:【1,4,6,12,2,1】
小猫鱼干:【1,2,3,4,2,1】
情况2:
小猫等级:【1,3,4,6,6,12】
小猫鱼干:【1,2,3,4,1,2】
显而易见,当出现这两种情况时,小猫鱼干分配不再遵从常规顺序递增情况。那么我们该如何处理特殊情况呢?
如果我们将小猫进行拆分,可以发现他们的数值其实也遵从局部单调函数的分布,具体如下图:
现在我们基于函数图形对数据分析,从第一个递增函数起点开始初始化为1,如果相邻小猫级别大于他,数量便加1,如果相邻小猫数量小于或等于他,代表目前不处在递增函数里面,所以将当前结点视为递增函数新起点,重新将起始点初始化为1,然后重复操作。可能说的有点抽象,那么接下来请看具体代码实现
代码展示
for (int i = 1; i < n; i++) {
if (levels[i] > levels[i - 1]) {
fishCount[i] = fishCount[i - 1] + 1;
}
}
考虑完递增情况,接下来介绍递减情况。递减时的函数形状可以视作小猫逆序情况下的递增函数,所以我们可以用相同的处理方法将其处理,那么接下来请看具体代码实现
代码展示
for (int i = n - 2; i >= 0; i--) {
if (levels[i] > levels[i + 1]) {
fishCount[i] = Math.max(fishCount[i], fishCount[i + 1] + 1);
}
}
当情况考虑完,这道题也就做出来了。可以发现这道题将情况考虑出来后其实并不难,难在你要发现这两种情况。就这道题而言你需要画图分析,所以这道题还是很有学习价值的。现在整道题讲解完毕,下面是完整代码
完整代码展示
import java.util.ArrayList;
import java.util.List;
public class Main {
public static int solution(int n, List<Integer> cats_levels) {
int[] levels = cats_levels.stream().mapToInt(i -> i).toArray();
int[] fishCount = new int[n];
// 初始化每只猫至少获得一斤鱼干
for (int i = 0; i < n; i++) {
fishCount[i] = 1;
}
// 从左到右遍历
for (int i = 1; i < n; i++) {
if (levels[i] > levels[i - 1]) {
fishCount[i] = fishCount[i - 1] + 1;
}
}
// 从右到左遍历
for (int i = n - 2; i >= 0; i--) {
if (levels[i] > levels[i + 1]) {
fishCount[i] = Math.max(fishCount[i], fishCount[i + 1] + 1);
}
}
// 计算总的鱼干数量
int sum = 0;
for (int fish : fishCount) {
sum += fish;
}
return sum;
}
public static void main(String[] args) {
List<Integer> catsLevels1 = new ArrayList<>();
catsLevels1.add(1);
catsLevels1.add(2);
catsLevels1.add(2);
List<Integer> catsLevels2 = new ArrayList<>();
catsLevels2.add(6);
catsLevels2.add(5);
catsLevels2.add(4);
catsLevels2.add(3);
catsLevels2.add(2);
catsLevels2.add(16);
List<Integer> catsLevels3 = new ArrayList<>();
catsLevels3.add(1);
catsLevels3.add(2);
catsLevels3.add(2);
catsLevels3.add(3);
catsLevels3.add(3);
catsLevels3.add(20);
catsLevels3.add(1);
catsLevels3.add(2);
catsLevels3.add(3);
catsLevels3.add(3);
catsLevels3.add(2);
catsLevels3.add(1);
catsLevels3.add(5);
catsLevels3.add(6);
catsLevels3.add(6);
catsLevels3.add(5);
catsLevels3.add(5);
catsLevels3.add(7);
catsLevels3.add(7);
catsLevels3.add(4);
System.out.println(solution(3, catsLevels1) == 4); // 应输出 true
System.out.println(solution(6, catsLevels2) == 17); // 应输出 true
System.out.println(solution(20, catsLevels3) == 35); // 应输出 true
}
}
收获与总结
这道题是一道非常经典的贪心算法题,难度和实现代码量都适中,很适合初学者练手。我讲解这道题的原因在于我见过很多类似的题目,他们的核心思路几乎没有变化,当完成他们中的一道题后,其他的题也就顺理成章的可以实现了。总的来说这道题的学习意义大于提升意义,对于大佬来说,我的这篇文章有点浪费时间,但对于代码基础不是那么强的,我相信这道题可以教会你遇到类似的题应该如何思考,如何快速作出反应。以上分析仅为个人观点,如有不满,敬请开喷。