跳至主要內容

森林中的兔子

周子力大约 5 分钟教学文档动态规划

题目描述

picture 0
picture 0

解题思路

🌲 问题回顾

每只被问到的兔子会回答:“还有多少只兔子和我颜色相同?”
注意:它不包括自己

  • 如果一只兔子说 k,那么说明与它同色的兔子总数是 k + 1(包括它自己)。
  • 我们的目标是:在满足所有兔子回答的前提下,让森林中的总兔子数最少

✅ 示例 1:answers = [1, 1, 2]

步骤分析:

第一步:统计每个回答的数量

  • 回答 1 的有 2 只兔子。
  • 回答 2 的有 1 只兔子。

我们记:

  • count[1] = 2
  • count[2] = 1

第二步:处理回答为 1 的兔子

  • 每只说 1 的兔子意味着:“除了我之外还有 1 只和我一样颜色”,所以这个颜色组总共应有 1 + 1 = 2 只兔子。
  • 现在有 2 只兔子都说 1

能不能让这 2 只兔子属于同一个颜色?

可以!因为一个颜色最多容纳 2 只兔子,现在正好有 2 只。
→ 所以它们可以是一对同色兔子(比如都是红色)。
→ 这个颜色组就完整了,不需要额外兔子。

✅ 贡献总数:2 只(都已提问)


第三步:处理回答为 2 的兔子

  • 它说:“还有 2 只和我颜色相同”,所以这个颜色组应该有 2 + 1 = 3 只兔子。
  • 但现在只有 1 只兔子说了 2

那么另外 2 只同色兔子呢?它们可能没被提问!

为了使总数最小,我们假设这只兔子是一个新颜色组的代表。
→ 这个颜色组必须存在 3 只兔子,但只有 1 只被问到了。

✅ 所以我们要补上 2 只未被提问的蓝色兔子

→ 这个颜色组贡献:3 只兔子。


第四步:汇总总数

  • 颜色 A(红):2 只(都被提问)
  • 颜色 B(蓝):3 只(1 只被提问,2 只未被提问)

🟡 最少总兔子数 = 2 + 3 = 5

✔️ 输出:5


✅ 示例 2:answers = [10, 10, 10]

统计:

  • count[10] = 3
  • 每个说 10 的兔子表示其颜色组共有 10 + 1 = 11 只兔子。

分析:

  • 有 3 只兔子说 10
  • 一个颜色组最多能容纳 11 只兔子,而目前只有 3 只提到该颜色。

能不能让这 3 只兔子属于同一个颜色?

完全可以!因为只要不超过 11 只,都可以算作同一颜色。

所以我们只需要创建一个颜色组(比如紫色),包含:

  • 3 只被提问的兔子
  • 再加上 8 只未被提问的同色兔子

→ 总共 11 只兔子即可满足条件。

⚠️ 注意:不能分成多个组来减少总数。例如,如果强行分两组,则每组都要有 11 只 → 至少需要 11 * 2 = 22 只,远大于 11。

所以最优策略是:尽可能把相同回答的兔子合并到同一个颜色组中

🟢 贡献总数:11

✔️ 输出:11


✅ 示例 3:answers = [0, 0, 0]

分析:

  • 每只兔子说 0 → 表示“没有其他兔子和我颜色相同”
  • 即:我自己就是唯一的这种颜色的兔子

所以:

  • 每只说 0 的兔子必须是独一无二的颜色。
  • 有 3 只兔子说 0 → 就要有 3 种不同的颜色,每种颜色只有 1 只兔子。

🟢 总数 = 3

✔️ 输出:3


✅ 示例 4:answers = [2, 2, 2, 2]

统计:

  • count[2] = 4
  • 每个颜色组最多可容纳 2 + 1 = 3 只兔子。

分组策略:

  • 有 4 只兔子都说 2
  • 但我们知道,一个颜色组最多只能有 3 只兔子
  • 所以这 4 只兔子不可能全在一个组里

怎么办?我们必须将它们分成至少两个颜色组。

尝试分配:

  • 第一组:3 只兔子(都回答 2)→ 合法,组成颜色 A(共 3 只)
  • 剩下 1 只兔子 → 必须属于另一个颜色组 B
    • 它也说“还有 2 只和我一样”,所以颜色组 B 必须有 3 只兔子
    • 但它自己是唯一被问到的 → 需要再补 2 只未被提问的兔子

所以:

  • 颜色 A:3 只(3 只被提问)
  • 颜色 B:3 只(1 只被提问,2 只未被提问)

🟢 总数 = 3 + 3 = 6

📌 关键点:虽然只有 1 只兔子留在第二组,但由于它的回答决定了整个组的大小,我们必须补足。

我们可以用公式计算:

  • group_size = 2 + 1 = 3
  • num_groups = ceil(4 / 3) = 2
  • 贡献总数 = 2 * 3 = 6

✔️ 输出:6


✅ 示例 5:answers = [](空数组)

没有任何兔子被提问。

我们无法得知任何信息,但问题是“最少数量”。

在这种情况下,最合理假设是:森林中没有兔子被看到或提问,但可能为空

不过题目隐含前提是有兔子存在才提问,所以若无提问,则兔子数为 0。

✔️ 输出:0


🔍 总结规律(贪心核心)

对于每个回答 k 出现了 c 次:

  1. 每个颜色组最多容纳 g = k + 1 只兔子。
  2. 最少需要的组数为:c/g\lceil c / g \rceil(向上取整)
  3. 每组有 g 只兔子 → 总贡献:c/g×g\lceil c / g \rceil \times g

最终答案就是对所有不同 k 的贡献求和。


🧠 关键思维:为什么这是“贪心”?

因为我们总是试图:

把尽可能多的回答相同的兔子归入同一个颜色组,从而最小化总的组数和总兔子数。

这是一种典型的局部最优策略:每一步都最大化利用当前颜色组的空间,避免不必要的浪费。


✅ Python 代码(带注释)

from collections import Counter

def numRabbits(answers):
    count = Counter(answers)
    total = 0
    for k, cnt in count.items():
        group_size = k + 1
        # 向上取整:(cnt + group_size - 1) // group_size
        num_groups = (cnt + group_size - 1) // group_size
        total += num_groups * group_size
    return total

# 测试
print(numRabbits([1,1,2]))     # 5
print(numRabbits([10,10,10]))  # 11
print(numRabbits([0,0,0]))     # 3
print(numRabbits([2,2,2,2]))   # 6
print(numRabbits([]))          # 0

✅ 结论

通过这些例子可以看出,解决此类问题的关键在于:

  1. 理解“回答 k”意味着“本色组共 k+1 只”
  2. 相同回答的兔子尽量合并到同一组(贪心)
  3. 当数量超过组容量时,必须拆分为多个独立组
  4. 每个组即使只有 1 只兔子被问到,也要补足整个组的规模

这样就能在保证所有回答一致的前提下,得到最少的总兔子数

上次编辑于:
贡献者: zilizhou