力扣hard——1420.生成数组(动态规划)
力扣第185场周赛最后一题生成数组,挺有意思的一道题
先来看看题目
题目解析:
首先,此题给出的代码就是一个在数组中寻找最大值的代码
其中的search_cost代表的就是在寻找过程中更新了几次最大值,也就是有多少对相邻的两个数是升序排列的。
再看题目要求,结合刚才的分析,可以把题目抽象成:
要求有多少个有n个整数、整数大小大于1小于m、有k对相邻的两个数是升序排列的数组的数量。
解题思路:
我们可以很快的想到一个暴力解法,枚举所有有n个整数、整数大小大于1小于m的数组,然后利用题目给出的代码检测这些数组的search_cost,为k则答案加1。
此暴力解法复杂度为O(n*(m^n)),这是一个指数级的复杂度,根据题目给出的n,m的范围,那么复杂度可以达到O(50*(100^50)),这个复杂度就是神威太湖之光也算不来。
接下来我们想想有什么办法可以优化,首先分治的思路对于这题肯定是行不通的,因为问题无法无法分割为两个独立的子问题。贪心算法也行不通,因为这个问题是要求一个数量,不是要求一个问题的最优解。
那么还有什么算法适合这个问题呢?
动态规划可行吗?
要判断动态规划是否可行,就要先来看看这个问题是否可以分解为几个重叠的子问题。
记长度为n-1,数组中最大值为m,search_cost为p的数组为A0,这样的数组数量为x0
记长度为n-1,数组中最大值为i(1=<i<m),search_cost为p-1的数组为Ai,这样的数组数量为xi
记长度为n,数组中最大值为m,search_cost为p的数组为B,这样的数组数量为y
我们仔细想想,数组A0后面加上任意一个值在[1,m]之间的数是不是就变成了数组B,而数组Ai后面加上任意一个值在[i+1,m]之间的数是不是也变成了数组B。这就对了,数组B就是由数组A0-Am在后面添加一个数演变过来的。所以这道题可以分解为几个重叠子问题,可以用动态规划求解。
那么我们设dp[n][m][p]表示长度为n,数组中最大值为m,search_cost为p的数组数量,可以得到如下的状态转移方程
此算法复杂度O(n*m*k),按照题目给出的范围也就是O(250000),这个复杂度完全可以通过,OK,接下来就是码代码,注意由于结果太大,所以结果要对1000000007取模。
代码
class Solution {
public static final int mod = 1000000007;
public int numOfArrays(int n, int m, int k) {
if (m<k||n<k)//不存在这样的数组
return 0;
int[][][] dp = new int[n + 1][m + 1][k + 1];
for (int i = 1;i<=n;i++)//初始化,全为1的数组只有一个
dp[i][1][1] = 1;
for (int j = 1;j<=m;j++)//初始化,长度为1,最大值为j,cost为1的数组只有一个
dp[1][j][1] = 1;
for (int i = 2;i<=n;i++)
for (int p = 1;p<=k;p++)
for(int j = 2,sum = dp[i-1][1][p-1];j<=m;j++,sum=(sum+dp[i-1][j-1][p-1])%mod)
dp[i][j][p] = (sum+(int)(((long)dp[i-1][j][p]*j)%mod))%mod;
int ans = 0;
for (int j = 1;j<=m;j++)//最后结果为长为n,最大值为j,cost为k的数组数量的和
ans = (ans+dp[n][j][k])%mod;
return ans;
}
}