古董键盘

时间:2022-07-25
本文章向大家介绍古董键盘,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

问题描述

小扣在秋日市集购买了一个古董键盘。由于古董键盘年久失修,键盘上只有 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]即为所求。

转移方程

dp[i][j] = sum_{x = 0}^{k} dp[i - 1][j - x] * C_{j}^{x}

x指的是当前选择x个i字母,C(j, x)表示从j个位置中选择x个位置用于存放字母i。因此只需要从0到k枚举x即可。

baseline:

dp[i][0] = 1 quad iin[0,26]\dp[0][j] = 0 quad iin[1,n]

第一个式子含义为使用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;
        }
    }
}