[LeetCode解题报告] 1420. 生成数组

一、 题目

1. 题目描述

给你三个整数 nmk 。下图描述的算法用于找出正整数数组中最大的元素。

请你生成一个具有下述属性的数组 arr

  • arr 中有 n 个整数。
  • 1 <= arr[i] <= m 其中 (0 <= i < n)
  • 将上面提到的算法应用于 arrsearch_cost 的值等于 k

返回上述条件下生成数组 arr方法数 ,由于答案可能会很大,所以 必须10^9 + 7 取余。

 

示例 1:

输入:n = 2, m = 3, k = 1 输出:6 解释:可能的数组分别为 [1, 1], [2, 1], [2, 2], [3, 1], [3, 2] [3, 3]

示例 2:

输入:n = 5, m = 2, k = 3
输出:0
解释:没有数组可以满足上述条件

示例 3:

输入:n = 9, m = 1, k = 1 输出:1 解释:可能的数组只有 [1, 1, 1, 1, 1, 1, 1, 1, 1]

示例 4:

输入:n = 50, m = 100, k = 25 输出:34549172 解释:不要忘了对 1000000007 取余

示例 5:

输入:n = 37, m = 17, k = 7 输出:418930126

 

提示:

  • 1 <= n <= 50
  • 1 <= m <= 100
  • 0 <= k <= n
Related Topics
  • 动态规划

  • 👍 69
  • 👎 0

2. 原题链接

链接: 1420. 生成数组

二、 解题报告

1. 思路分析

  • 撑了一下午不看题解,自己写了个*O(n2m2k)*版的。TLE了
  • 看了下题解,终于做了下:
    1. 令f(n,m,k)为长度n的数组,最大数为m(注意必须用到m),上升次数为k的数组个数。
    2. 我们考虑数组中最后一个数,即f(i,mi,ki)会从f(i-1,…,…)中转移过来。
    3. 如果最后一个数的加入,没对k产生影响,则前n-1个数已经满足了上升k次,最后一个数就可以从[1,m]随便选。
      则有 m*f(n-1,m,k)种组合。
    4. 如果最后一个数的加入,使k产生了变化,那一定让k+1,则这个数一定是m,前边的数没有m。
      则有 sum{f(n-1,j,k-1)|j∈[1,m]}种组合
    5. 如果递归做需要考虑递归出口
    6. 官方题解用dp,甚至可以前缀和优化。

2. 复杂度分析

TLE版dfs: O(n2m2k)
普通dp或dfs: O(nm2k)
前缀和优化dp:O(nmk)

3. 代码实现

普通dfs


class Solution:
    def numOfArrays(self, n: int, m: int, k: int) -> int:
        mod = 10**9+7
        """题意:生成n个1到m之间([1,m])的数,且前缀最大值只上升k次(且最大的那个数最先出现的位置,从头计数max,必须上升k次)。
        问有几种生成方法
        """
        if k == 0:
            return 0  

        @cache
        def dfs(n,m,k):       
            if m == 1:
                if k == 1:
                    return 1
                return 0
            if m < k:
                return 0
            if n == 1:
                if k == 1:
                    return 1
                return 0
                
            ans = dfs(n-1,m,k)*m
            # print(n,m,k,ans)
            for j in range(1,m):                
                ans = (ans + (dfs(n-1,j,k-1))%mod)%mod
                    
            return ans
        ret = 0
        for i in range(1,m+1):
            ret = (ret+dfs(n,i,k))%mod 
        return ret

"""
123  √
213  ×
"""
"""
11
22
21
"""

"""看了题解n*m*k*m,自己写:
1.令生成n个数上升k次的答案是:dfs(n,m,k),其中m是用了的数里的最大值
2.假设第n个数不改变k,那么这第n个数一定小于等于m,前n-1个数已经是一个完全满足k且最大值是m的子数组,共有dfs(n-1,m,k)*m
3.假设第n个数改变了k,那么这第n个数一定是m,前n-1个数里都小于m,那么前n-1个数的状态共有sum{dfs(n-1,j,k-1)|j∈[1,m-1]}
4.考虑出口:
    
"""

TLE版,请勿参考:


class Solution:
    def numOfArrays(self, n: int, m: int, k: int) -> int:
        mod = 10 ** 9 + 7
        """题意:生成n个1到m之间([1,m])的数,且前缀最大值只上升k次(且最大的那个数最先出现的位置,从头计数max,必须上升k次)。
        问有几种生成方法
        """
        if k == 0:
            return 0

        @cache
        def q_pow1(a, b):  # 快速幂
            if b == 0:
                return 1
            if b == 1:
                return a
            if b & 1 == 0:
                return q_pow(a * a % mod, b >> 1)
            else:
                return q_pow(a * a % mod, b >> 1) * a % mod

        def q_pow(base, power):
            res = 1
            while power > 0:
                if power & 1 == 1:
                    res = res * base % mod
                power >>= 1
                base = base * base % mod
            return res % mod
        
        @cache
        def dfs(n, m, k):
            if n == 0:
                if k == 0:
                    return 1
                return 0
            if m == 1:
                if k == 1:
                    return 1
                return 0
            if k == 0:
                if n == 0:
                    return 1
                return 0
            ans = 0
            # 遍历最大数x,它下标是j
            for j in range(k - 1, n):  # j前边至少要有k-1个数
                for x in range(k, m + 1):  # 比x小的数至少要有k-1个
                    r = q_pow(x, n - j - 1)
                    l = dp[j][x-1][k-1] = dfs(j, x - 1, k - 1)
                    ans = (ans % mod + (l % mod * r ) % mod) % mod
            return ans

        return dfs(n, m, k)

三、 本题小结

  1. 反正dp的题我看透了,我只能做出来我做的出来的题,做不出来的题下次遇到还是做不出来。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值