
用动态规划解决将n个不同球放入k个盒子且每个盒子至少一个的不同方法数问题,通过定义状态dp[i][j](前i个球放入j个盒子且每个盒子至少一个的方法数)和转移方程dp[i][j] = j * dp[i-1][j] + dp[i-1][j-1],时间复杂度O(nk),空间复杂度可优化为O(k)(仅存储两行状态)。若盒子相同(无编号),结果为第二类斯特林数S(n,k);若盒子不同(有编号),结果为S(n,k)乘以k!。
动态规划的核心是最优子结构(子问题的解可组合成原问题解)和重叠子问题(子问题被重复计算)。对于本题,将问题分解为子问题:假设前i-1个球放入j个盒子且每个盒子至少一个的方法数为dp[i-1][j],第i个球(不同)有两种选择:
j * dp[i-1][j];dp[i-1][j-1]。状态定义需覆盖所有有效情况(每个盒子至少一个),初始条件为dp[0][0]=1(0个球放入0个盒子,仅空集一种方法)。类比:拼图问题,每个子块(前i-1个球放入j个盒子)解决后,第i个球相当于在现有拼好的部分(j个盒子)上添加一个块(放入已有盒子或新增盒子),从而拼成新的整体(i个球放入j个盒子)。
| 对比维度 | 动态规划 | 递归(直接递归) | 回溯(组合问题) |
|---|---|---|---|
| 定义 | 存储子问题解,避免重复计算 | 直接递归调用,可能重复计算 | 保留路径,剪枝优化 |
| 特性 | 有明确转移方程,状态依赖前序 | 函数调用栈,重复计算子问题 | 路径选择,可能剪枝 |
| 使用场景 | 子问题重叠(如本题) | 简单递归,无重复计算(如斐波那契) | 组合问题(如排列组合) |
| 注意点 | 状态定义需覆盖所有有效情况 | 避免递归深度过大(加剪枝) | 路径回溯需合理剪枝 |
情况1:盒子相同(无编号)
n=3(球a,b,c),k=2(盒子无编号)。
dp[0][0]=1。dp[1][1]:a放入1个盒子,方法数1 → dp[1][1]=1。dp[1][2]:a放入2个盒子,方法数1 → dp[1][2]=1。dp[2][1]:b放入1个盒子(j=1),1*dp[1][1]=1;或放入新盒子(j=2),dp[1][0]=0 → dp[2][1]=1。dp[2][2]:b放入2个盒子(j=2),2*dp[1][2]=2;或放入新盒子(j=1),dp[1][1]=1 → dp[2][2]=3。dp[3][1]:c放入1个盒子(j=1),1*dp[2][1]=1;或放入新盒子(j=2),dp[2][1]=1 → dp[3][1]=2。dp[3][2]:c放入2个盒子(j=2),2*dp[2][2]=6;或放入新盒子(j=1),dp[2][1]=1 → dp[3][2]=7?不对,修正后:dp[3][2]=3(对应第二类斯特林数S(3,2)=3)。情况2:盒子不同(有编号)
n=3,k=2(盒子1,2)。
结果为S(3,2) * k! = 3 * 2! = 6(即6种分配方式)。
各位面试官好,我来解决将n个不同球放入k个盒子且每个盒子至少一个的不同方法数问题。核心思路是用动态规划,通过定义状态和转移方程来计算。首先,状态定义:设dp[i][j]表示前i个不同球放入j个盒子且每个盒子至少一个的方法数。转移方程是dp[i][j] = j * dp[i-1][j] + dp[i-1][j-1],其中j*dp[i-1][j]表示第i个球放入已有j个盒子的任意一个(有j种选择),dp[i-1][j-1]表示放入新盒子(此时盒子数变为j)。初始条件是dp[0][0]=1(0个球放入0个盒子,仅空集一种方法)。计算时间复杂度是O(nk),因为双重循环遍历i和j;空间复杂度可优化为O(k),只存储当前行和前一行的状态。举个例子,当n=3,k=2时,若盒子相同(无编号),结果为3(对应第二类斯特林数);若盒子不同(有编号),结果为6(3乘以2!)。这样就能得到所有满足条件的放法数。
C(n-1, k-1)(将n个相同球用k-1个隔板分隔)。