leetcode-生成数组

 题目是LeetCode第185场周赛的第四题,链接:生成数组。具体描述见原题。

 这道题是hard难度的,然后不出意料又是动态规划的题目,难点自然在于递推公式。定义dp[i][j][p]代表对于前i个数,最大的数为j,然后search cost为p时的符合条件的数组总数。那么对于前i个数且最大值为j、search cost为k的这种情况,又可以分为两种情况:

  • 最大值j在位置1~i-1之间,则第i个位置上的数可以是1~j,则此时的总数为j * dp[i-1][j][k],这里的dp[i-1][j][k]就是前i-1个数里刚好最大值为j且search cost为k时的总数;
  • 最大值j在第i个位置,则此时的方法数就是前i-1个数最大值为1~j-1j-1种情况、search cost为k-1j-1个总数之和: ∑ p = 1 j − 1 d p [ i − 1 ] [ p ] [ k − 1 ] \sum_{p=1}^{j-1}dp[i-1][p][k-1] p=1j1dp[i1][p][k1]

 总结一下,就是 d p [ i ] [ j ] [ k ] = j × d p [ i − 1 ] [ j ] [ k ] + ∑ p = 1 j − 1 d p [ i − 1 ] [ p ] [ k − 1 ] dp[i][j][k]=j\times dp[i-1][j][k]+\sum_{p=1}^{j-1}dp[i-1][p][k-1] dp[i][j][k]=j×dp[i1][j][k]+p=1j1dp[i1][p][k1]。然后边界条件为dp[i][1][1]=1(对应于全部i个数全为1的情况)以及dp[1][j][1]=1(对应于只有一个数的情况,这个数只能是j)。最终的结果就是 ∑ p = 1 m d p [ n ] [ p ] [ k ] \sum_{p=1}^{m}dp[n][p][k] p=1mdp[n][p][k]

 时间复杂度为 O ( n m 2 k ) O(nm^{2}k) O(nm2k),空间复杂度为 O ( m n k ) O(mnk) O(mnk)

 JAVA版代码如下:

class Solution {
    public int numOfArrays(int n, int m, int k) {
        if (k == 0 || m < k) {
            return 0;
        }
        int mod = 1000_000_007;
        // dp[i][j][p]:前i个数,最大的为j,search cost为p
        int[][][] dp = new int[n + 1][m + 1][k + 1];
        for (int i = 1; i <= n; ++i) {
            dp[i][1][1] = 1;
        }
        for (int j = 1; j <= m; ++j) {
            dp[1][j][1] = 1;
        }
        for (int i = 2; i <= n; ++i) {
            for(int j = 2; j <= m; ++j) {
                for (int p = 1; p <= k; ++p) {
                    int sum = 0;
                    for (int q = 1; q <= Math.min(j - 1, m); ++q) {
                        sum = (sum + dp[i - 1][q][p - 1]) % mod;
                    }
                    int temp = (int)((dp[i - 1][j][p] * 1L * j) % mod);
                    dp[i][j][p] = (sum + temp) % mod;
                }
            }
        }
        int result = 0;
        for (int j = 1; j <= m; ++j) {
            result = (result + dp[n][j][k]) % mod;
        }
        return result % mod;
    }
}

 提交结果如下:


 上面的代码有个问题就是求 ∑ p = 1 j − 1 d p [ i − 1 ] [ p ] [ k − 1 ] \sum_{p=1}^{j-1}dp[i-1][p][k-1] p=1j1dp[i1][p][k1]费时得很,但其实只要我们稍微改一下三个循环的顺序,把对p的循环提到对j的循环之前就可以借助于对j的循环求得上面的求和。从而减少时间复杂度为 O ( n m k ) O(nmk) O(nmk),空间复杂度还是 O ( m n k ) O(mnk) O(mnk)

 JAVA版代码如下:

class Solution {
    public int numOfArrays(int n, int m, int k) {
        if (k == 0 || m < k) {
            return 0;
        }
        int mod = 1000_000_007;
        // dp[i][j][p]:前i个数,最大的为j,search cost为p
        int[][][] dp = new int[n + 1][m + 1][k + 1];
        for (int i = 1; i <= n; ++i) {
            dp[i][1][1] = 1;
        }
        for (int j = 1; j <= m; ++j) {
            dp[1][j][1] = 1;
        }
        for (int i = 2; i <= n; ++i) {
            for (int p = 1; p <= k; ++p) {
                int sum = 0;
                for(int j = 2; j <= m; ++j) {
                    sum = (sum + dp[i - 1][j - 1][p - 1]) % mod;
                    int temp = (int)((dp[i - 1][j][p] * 1L * j) % mod);
                    dp[i][j][p] = (sum + temp) % mod;
                }
            }
        }
        int result = 0;
        for (int j = 1; j <= m; ++j) {
            result = (result + dp[n][j][k]) % mod;
        }
        return result % mod;
    }
}

 提交结果如下:


 Python版代码如下:

class Solution:
    def numOfArrays(self, n: int, m: int, k: int) -> int:
        if k == 0 or k > m:
            return 0
        mod = 1000000007
        dp = [[[0 for _ in range(k + 1)] for _ in range(m + 1)] for _ in range(n + 1)]
        for i in range(1, n + 1):
            dp[i][1][1] = 1
        for j in range(1, m + 1):
            dp[1][j][1] = 1
        for i in range(2, n + 1):
            for p in range(1, k + 1):
                s = 0
                for j in range(2, m + 1):
                    s = (s + dp[i - 1][j - 1][p - 1]) % mod
                    dp[i][j][p] = (s + j * dp[i - 1][j][p]) % mod
        result = 0
        for j in range(1, m + 1):
            result += dp[n][j][k]
        return result % mod

 提交结果如下:


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值