小扣在秋日市集购买了一个古董键盘。由于古董键盘年久失修,键盘上只有 26 个字母 a~z 可以按下,且每个字母最多仅能被按 k 次。
小扣随机按了 n 次按键,请返回小扣总共有可能按出多少种内容。由于数字较大,最终答案需要对 1000000007 (1e9 + 7) 取模。
示例 1:
输入:k = 1, n = 1
输出:26
解释:由于只能按一次按键,所有可能的字符串为 "a", "b", ... "z"
示例 2:
输入:k = 1, n = 2
输出:650
解释:由于只能按两次按键,且每个键最多只能按一次,所有可能的字符串(按字典序排序)为 "ab", "ac", ... "zy"
提示:
1 <= k <= 5
1 <= n <= 26*k
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/Uh984O
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
可以认为26个字母就是26种物品,其中每种物品的数量都为k。定义dp[i] [j] 为可选i种字母组成长度为j的字符串数目。
dp[26] [n]即为所求。
转移方程
x指的是当前选择x个i字母,C(j, x)表示从j个位置中选择x个位置用于存放字母i。因此只需要从0到k枚举x即可。
baseline:
第一个式子含义为使用i个字符构成长度为0的字符串数目只有一种;
第二个式子含义为当使用0个字符是不可能构成长度大于0的字符串,因此为0。
此外对于C(j, x)可以通过离线打表的方式获得,其递推式为C(n, m) = C(n - 1, m) + C(n-1,m-1)。
class Solution {
int mod = (int)1e9 + 7;
public int keyboard(int k, int n) {
int[][] C = new int[n + 1][n + 1];
init(C);
// dp[i][j] 表示使用 i 个单词,组成长度为j 的序列数目
long[][] dp = new long[27][n + 1];
for(int i = 0; i <= 26; i++){
dp[i][0] = 1;
}
// dp[0][j] = 0 j != 0
for(int i = 1; i <= 26; i++){
for(int j = 1; j <= n; j++){
for(int x = 0; x <= k; x++){
if(j - x < 0){
break;
}
dp[i][j] = dp[i][j] + dp[i - 1][j - x] * C[j][x];
}
dp[i][j] %= mod;
}
}
return (int)dp[26][n];
}
public void init(int[][] C){
for(int i = 1; i < C.length; i++){
C[i][0] = 1;
for(int j = 1; j < i; j++){
C[i][j] = (int)(((long)C[i - 1][j - 1] + C[i - 1][j]) % mod);
}
C[i][i] = 1;
}
}
}