LC 2305. 公平分发饼干

题目描述

这是 LeetCode 上的 2305. 公平分发饼干 ,难度为 中等

给你一个整数数组 cookies,其中 cookies[i] 表示在第 i 个零食包中的饼干数量。另给你一个整数 k 表示等待分发零食包的孩子数量,所有零食包都需要分发。

在同一个零食包中的所有饼干都必须分发给同一个孩子,不能分开。

分发的不公平程度定义为单个孩子在分发过程中能够获得饼干的最大总数。

返回所有分发的最小不公平程度。

示例 1:

1
2
3
4
5
6
7
8
9
输入:cookies = [8,15,10,20,8], k = 2

输出:31

解释:一种最优方案是 [8,15,8] 和 [10,20] 。
- 第 1 个孩子分到 [8,15,8] ,总计 8 + 15 + 8 = 31 块饼干。
- 第 2 个孩子分到 [10,20] ,总计 10 + 20 = 30 块饼干。
分发的不公平程度为 max(31,30) = 31
可以证明不存在不公平程度小于 31 的分发方案。

示例 2:
1
2
3
4
5
6
7
8
9
10
输入:cookies = [6,1,3,2,2,4,1,2], k = 3

输出:7

解释:一种最优方案是 [6,1]、[3,2,2] 和 [4,1,2] 。
- 第 1 个孩子分到 [6,1] ,总计 6 + 1 = 7 块饼干。
- 第 2 个孩子分到 [3,2,2] ,总计 3 + 2 + 2 = 7 块饼干。
- 第 3 个孩子分到 [4,1,2] ,总计 4 + 1 + 2 = 7 块饼干。
分发的不公平程度为 max(7,7,7) = 7
可以证明不存在不公平程度小于 7 的分发方案。

提示:

  • $2 <= cookies.length <= 8$
  • $1 <= cookies[i] <= 10^5$
  • $2 <= k <= cookies.length$

状压 DP

为了方便,将 cookies 记为 cs

定义 $f[i][s]$ 为考虑前 $i$ 个人,对 cs 的分配情况为 s 时的最小不公平程度

其中 s 为一个二进制数,若 s 的第 i 位为 1 代表 cs[i] 已被分配,反之代表未分配。同时我们可以预处理 g 数组,g[s] = t 含义为选择 cs 状态为 s 时得到的饼干总和为 t

初始化只有 $f[0][0] = 0$,其余均为正无穷 0x3f3f3f3f

不失一般性考虑 $f[i][s]$ 该如何计算,通过枚举第 $i$ 个小朋友所分配到的饼干情况 ps 的子集)进行转移:若第 $i$ 个小朋友分配到的饼干情况为 p,则此前 $i - 1$ 个小朋友分配到饼干情况为 $s - p$,前 $i - 1$ 个小朋友的最小不公平程度为 $f[i - 1][s - p]$,当前第 $i$ 个小朋友的不公平程度为 $g[p]$,两者中最大值可用于更新 $f[i][s]$。

最终 $f[k][2^n - 1]$ 即是答案。

Java 代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Solution {
public int distributeCookies(int[] cs, int k) {
int n = cs.length, mask = 1 << n, INF = 0x3f3f3f3f;
int[] g = new int[mask];
for (int s = 0; s < mask; s++) {
int t = 0;
for (int i = 0; i < n; i++) t += ((s >> i) & 1) == 1 ? cs[i] : 0;
g[s] = t;
}
int[][] f = new int[k + 10][mask];
for (int i = 0; i <= k; i++) Arrays.fill(f[i], INF);
f[0][0] = 0;
for (int i = 1; i <= k; i++) {
for (int s = 0; s < mask; s++) {
for (int p = s; p != 0; p = (p - 1) & s) {
f[i][s] = Math.min(f[i][s], Math.max(f[i - 1][s - p], g[p]));
}
}
}
return f[k][mask - 1];
}
}

Python 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Solution:
def distributeCookies(self, cs: List[int], k: int) -> int:
n, mask, INF = len(cs), 1 << len(cs), 0x3f3f3f3f
g = [0] * mask
for s in range(mask):
t = 0
for i in range(n):
t += cs[i] if (s >> i) & 1 == 1 else 0
g[s] = t
f = [[INF] * mask for _ in range(k + 10)]
f[0][0] = 0
for i in range(1, k + 1):
for s in range(mask):
p = s
while p != 0:
f[i][s] = min(f[i][s], max(f[i - 1][s - p], g[p]))
p = (p - 1) & s
return f[k][mask - 1]

C++ 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public:
int distributeCookies(vector<int>& cs, int k) {
int n = cs.size(), mask = 1 << n, INF = 0x3f3f3f3f;
vector<int> g(mask, 0);
for (int s = 0; s < mask; s++) {
int t = 0;
for (int i = 0; i < n; i++) t += ((s >> i) & 1) == 1 ? cs[i] : 0;
g[s] = t;
}
vector<vector<int>> f(k + 10, vector<int>(mask, INF));
for (int i = 0; i <= k; i++) fill(f[i].begin(), f[i].end(), INF);
f[0][0] = 0;
for (int i = 1; i <= k; i++) {
for (int s = 0; s < mask; s++) {
for (int p = s; p != 0; p = (p - 1) & s) {
f[i][s] = min(f[i][s], max(f[i - 1][s - p], g[p]));
}
}
}
return f[k][mask - 1];
}
};

TypeScirpt 代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
function distributeCookies(cs: number[], k: number): number {
const n = cs.length, mask = 1 << n, INF = 0x3f3f3f3f;
const g = new Array(mask).fill(0);
for (let s = 0; s < mask; s++) {
let t = 0;
for (let i = 0; i < n; i++) t += ((s >> i) & 1) === 1 ? cs[i] : 0;
g[s] = t;
}
const f = new Array(k + 10).fill(0).map(() => new Array(mask).fill(INF));
f[0][0] = 0;
for (let i = 1; i <= k; i++) {
for (let s = 0; s < mask; s++) {
for (let p = s; p != 0; p = (p - 1) & s) {
f[i][s] = Math.min(f[i][s], Math.max(f[i - 1][s - p], g[p]));
}
}
}
return f[k][mask - 1];
};

  • 时间复杂度:将 cs 长度记为 $n$,状态数量记为 $m = 2^n$,预处理复杂度为 $O(n \times m)$;DP 过程需要枚举二进制长度为 $n$ 的所有子集的子集,复杂度为 $O(3^n)$,DP 过程复杂度为 $O(k \times 3^n)$。整体复杂度为 $O(k \times 3^n)$
  • 空间复杂度:$O(k \times m)$

最后

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

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

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

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


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!