题目描述
题目链接:LeetCode 2929. 给小朋友们分糖果 II(中等)
给你两个正整数 n 和 limit 。
请你将 n 颗糖果分给 3 位小朋友,确保没有任何小朋友得到超过 limit 颗糖果,请你返回满足此条件下的 总方案数 。
示例 1:
输入:n = 5, limit = 2
输出:3
解释:总共有 3 种方法分配 5 颗糖果,且每位小朋友的糖果数不超过 2 :(1, 2, 2) ,(2, 1, 2) 和 (2, 2, 1) 。
示例 2:
输入:n = 3, limit = 3
输出:10
解释:总共有 10 种方法分配 3 颗糖果,且每位小朋友的糖果数不超过 3 :(0, 0, 3) ,(0, 1, 2) ,(0, 2, 1) ,(0, 3, 0) ,(1, 0, 2) ,(1, 1, 1) ,(1, 2, 0) ,(2, 0, 1) ,(2, 1, 0) 和 (3, 0, 0) 。
提示:
1 <= n <= 10 6 10^{6} 106
1 <= limit <= 10 6 10^{6} 106
递归解法推导
-
考虑更通用的场景: n n n 颗糖果分给 x x x 个人,每个人最多 l i m i t limit limit 颗糖果。
-
整体思路框架:使用递归的方式搜索合法的分配方式进行汇总统计。
-
递归解法推导:
- 定义 d f s ( n , x , l i m i t ) dfs(n,x,limit) dfs(n,x,limit)为 “ n n n 颗糖果分给 x x x 个人,每个人最多 l i m i t limit limit 颗糖果” 的方案数。对于当前的人,我们尝试给他分 i i i 颗糖果,由于每个人最多 l i m i t limit limit 颗糖果,当前剩余糖果数量为 n n n ,所以 i ∈ [ 0 , min ( limit , n ) ] i \in [0, \min(\text{limit}, n)] i∈[0,min(limit,n)],得到递归公式为 dfs ( n , x , limit ) = ∑ i = 0 min ( limit , n ) dfs ( n − i , x − 1 , limit ) \text{dfs}(n, x, \text{limit}) = \sum_{i=0}^{\min(\text{limit}, n)} \text{dfs}(n-i, x-1, \text{limit}) dfs(n,x,limit)=∑i=0min(limit,n)dfs(n−i,x−1,limit)。
- 递归终止条件为 x = 1 x=1 x=1,也就是分到最后 1 1 1 个人的时候结束分配。前面的人都分配到了糖果(且前面的人分到的糖果数量都小于等于 l i m i t limit limit ,都是合法的取值),剩余的糖果就只能分配给最后一个人。如果剩余的糖果数量小于等于 l i m i t limit limit ,则形成一种合法的分配方案,返回 1 1 1 即可。如果剩余的糖果数量大于 l i m i t limit limit,则当前分配不合法,返回 0 0 0 。
- 下界优化:分析当前这个人最少能分几颗糖果。尽量让其他人多分糖果,那当前这个人分到的糖果就是最少。其他人都按最多的 l i m i t limit limit 分,则当前这个人最少要分的数量为 n − ( x − 1 ) ∗ l i m i t n-(x-1)*limit n−(x−1)∗limit。最终优化后的递归公式为 dfs ( n , x , limit ) = ∑ i = max ( 0 , n − ( x − 1 ) ⋅ limit ) min ( limit , n ) dfs ( n − i , x − 1 , limit ) \text{dfs}(n, x, \text{limit}) = \sum_{i=\max(0, n-(x-1)\cdot \text{limit})}^{\min(\text{limit}, n)} \text{dfs}(n-i, x-1, \text{limit}) dfs(n,x,limit)=∑i=max(0,n−(x−1)⋅limit)min(limit,n)dfs(n−i,x−1,limit)
- 时间复杂度分析: n n n 个人都尝试分配 i i i 颗糖果,且 i ∈ [ max ( 0 , n − ( x − 1 ) , min ( limit , n ) ] i \in [\max(0, n-(x-1), \min(\text{limit}, n)] i∈[max(0,n−(x−1),min(limit,n)],所以复杂度粗略为 l i m i t n limit^{n} limitn。
-
数学逻辑优化:
- x x x 个人,每个人最多分 l i m i t limit limit 颗糖果。假如每个人都按最多的数量分,那糖果数为 x ∗ l i m i t x*limit x∗limit 。当糖果数量 n > ( x ∗ l i m i t ) n>(x*limit) n>(x∗limit) 时,在 “每个人最多分 l i m i t limit limit 颗糖果” 的条件下,糖果怎么也分不完,所以不存在合法方案,直接返回 0 0 0
-
基于上述的分析推导,得到如下Java代码:
class Solution {
public long distributeCandies(int n, int limit) {
return dfs(3, n, limit);
}
public long dfs(int x, int n, int limit) {
if (x == 1) {
return n <= limit ? 1L : 0L;
}
if (n > x * limit) {
return 0;
}
long res = 0L;
int l = Math.max(0, n - (x - 1) * limit), r = Math.min(limit, n);
for (int i = l; i <= r; i++) {
res += dfs(x - 1, n - i, limit);
}
return res;
}
}
- 提交结果:当前解题方法时间复杂度较高,提交后超出时间限制。超出时间限制 522 / 958 个通过的测试用例。
超时解决
- 经过“数学逻辑优化” 优化后,部分不合法的分配会被提前终止。经过 “下界优化” 后,每个人分配 i i i 颗糖果 i ∈ [ max ( 0 , n − ( x − 1 ) , min ( limit , n ) ] i \in [\max(0, n-(x-1), \min(\text{limit}, n)] i∈[max(0,n−(x−1),min(limit,n)], i i i的取值都是合法取值。
- 当给倒数第二个人分配糖果的时候,方案数其实就已经确定了。根本不需要再递归到 x = 1 x=1 x=1。当 x = 2 x=2 x=2时, min ( limit , n ) − max ( 0 , n − ( x − 1 ) + 1 \min(\text{limit}, n)-\max(0, n-(x-1)+1 min(limit,n)−max(0,n−(x−1)+1 就是方案数。也就是把递归终止条件改为 x = 2 x=2 x=2即可, x = 2 x=2 x=2 时直接返回 min ( limit , n ) − max ( 0 , n − ( x − 1 ) + 1 \min(\text{limit}, n)-\max(0, n-(x-1)+1 min(limit,n)−max(0,n−(x−1)+1
AC代码
class Solution {
public long distributeCandies(int n, int limit) {
return dfs(3, n, limit);
}
public long dfs(int x, int n, int limit) {
if (n > x * limit) {
return 0;
}
int l = Math.max(0, n - (x - 1) * limit), r = Math.min(limit, n);
if (x == 2) {
return r - l + 1L;
}
long res = 0L;
for (int i = l; i <= r; i++) {
res += dfs(x - 1, n - i, limit);
}
return res;
}
}