参考博客 https://blog.csdn.net/qwb492859377/article/details/50654627
昨天是阿里网络笔试,实验室有一兄弟参加了,今天聊了聊发现有一个题特别有意思特别有启发,故拿出来表一表。
问题描述
题目大意是这样的:有 m
个鱼丸 n
个肉丸,将它们放到 k
个碗中有多少种放法。
要求:碗可以为空;碗的空间可以视为无限;鱼丸和肉丸不能放到同一个碗中;丸子之间碗之间没有区别。
取值范围: 1 <= m,n,k <= 50
求解
一看到这个问题感觉很像高中的排列组合问题,假设有一个函数 f(x, y)
可以计算出将 x
个丸子放到 y
个碗中所有放法,那么这道题就迎刃而解了:
def solution(m, n, k):
res = 0
for i in range(1, k):
res += f(m, i) * f(n, k-i)
res += f(n, i) * f(m, k-i)
return res
所以现在问题是写出函数 f(x, y)
。最开始是想着用递归来做:
def f(x, y):
if y == 0:
return 0
if x <= 1:
return 1
pass
两个特殊情况很容易写出来:如果碗的个数为0
显然无解返回0
;如果丸子个数小于等于1
,那么只有1
中放法,返回1
。然而关键部分的代码却理不清了,考虑用穷举法来做,即第一个碗里可以放 0 ~ x
个, 那么第二个碗,第三个碗…… 且不考虑复杂度,问题在于不能解决重复问题,以2个丸子放2个碗举例 (0, 2)
和 (2, 0)
是一样的。问题进入瓶颈。
参考 逍遥丶綦的博客发现,我们可以通过是否有空碗对所有情况进行分类。有空碗和无空碗是对立的,可以直接想加,不存在重叠也就是重复的问题。那么函数pass
部分可以这样写:
f(x, y) = f(x, y-1) + f(x-y, y)
f(x, y-1)
即将 x
个丸子放到 y-1
个碗中,保证空碗的存在;而 f(x-y, y)
即不存在空碗的情况下,y
个碗中至少都要有一个丸子,然后我们把剩下的 x-y
个丸子放到碗中即可。当然这里要考虑 x
和 y
的大小关系,如果 x-y < 0
的话等式的第二部分直接赋0即可。
写到这里就发现,可以用动态规划来做,上述等式即为状态转移方程。
可以写出代码:
def solution(m, n, k):
res = 0
m, n = max(m, n), min(m, n)
dp = [[0] * (k + 1) for _ in range(m + 1)]
# 初始化
for i in range(1, k + 1):
dp[0][i] = 1
dp[1][i] = 1
for i in range(1, m + 1):
dp[i][1] = 1
# 构造动态规划数组
for i in range(2, m + 1):
for j in range(1, k + 1):
dp[i][j] = dp[i][j - 1] + (dp[i - j][j] if i - j >= 0 else 0)
print(dp)
for i in range(1, k):
# 控制空碗
res += (dp[n][i] - dp[n][i - 1]) * (dp[m][k - i])
return res
特别需要注意的是控制空碗,即 res += (dp[n][i] - dp[n][i - 1]) * (dp[m][k - i])
,保证空碗只在放肉丸的时候出现,否则会出现重复。