class Solution {
public int numberOfStableArrays(int zero, int one, int limit) {
final long MOD = 1000000007;
long[][][] dp = new long[zero + 1][one + 1][2];
for (int i = 0; i <= Math.min(zero, limit); i++) {
dp[i][0][0] = 1;
}
for (int j = 0; j <= Math.min(one, limit); j++) {
dp[0][j][1] = 1;
}
for (int i = 1; i <= zero; i++) {
for (int j = 1; j <= one; j++) {
if (i > limit) {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1] - dp[i - limit - 1][j][1];
} else {
dp[i][j][0] = dp[i - 1][j][0] + dp[i - 1][j][1];
}
dp[i][j][0] = (dp[i][j][0] % MOD + MOD) % MOD;
if (j > limit) {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0] - dp[i][j - limit - 1][0];
} else {
dp[i][j][1] = dp[i][j - 1][1] + dp[i][j - 1][0];
}
dp[i][j][1] = (dp[i][j][1] % MOD + MOD) % MOD;
}
}
return (int) ((dp[zero][one][0] + dp[zero][one][1]) % MOD);
}
}
动态规划问题将会在我的另外一篇博客中详细讲解。
这里对官方答案进行解释:我们首先要知道,我们不是把所有数组生成了才去检测每个数组的子数组,而是再满足子数组条件的前提下,我们慢慢生成整个数组。
- 题目要求二进制数组 arr 中每个长度超过 imit 的子数组同时包含0和1,这个条件等价于二进制数组 ar 中每个长度等于limit +1的子数组都同时包含0和1(读者可以思考一下证明过程)。
这个很好理解,比如limit+2长度的子数组肯定包含之前的两个limit+1的子数组,那么如果之前limit+1的子数组都满足条件,那么limit+2长度的可能满足条件了。举例:limit=2,limit+2子数组为[0,1,2,3](其中数字为数组坐标),那么里面包含两个limit+1长度的子数组,分别是[0,1,2]、[1,2,3]。 - 按照题目要求,我们需要将 zero 个0和 one 个1依次填入二进制数组 arr,使用 dp0[i][j]表示已经填入i个0和j个1,并且最后填入的数字为 0的可行方案数目,dp1[i][j]表示已经填入i个0和j个1,并且最后填入的数字为1 的可行方案数目。对于dp0[i][j],我们分析一下它的转换方程:
这一段话需要注意我们相对应的代码为:long[][][] dp = new long[zero + 1][one + 1][2];
我们之后用最后一位的[0]来表示最后填入0,[1]表示最后填入1。注意下面都是以插入0来考虑,插入1通过同理可得。
- 对于上面这句话,我们要分成两种情况考虑,第一种是min(zero,limit)=zero,这时候零的个数比limit小或者与limit相同(在Java中,如果Math.min方法的两个参数相同,它将返回第一个参数。因为在数学上,给定两个相同的值,最小值将仍然是这两个值中的一个,所以返回第一个参数并没有任何问题。)说明了零的个数一定比limit+1小1或者更多,所以我们可以在某个子数组中把零全部填入,那么还会省下一个位置来放1,那么肯定满足条件:同时包含0和1。所以我们至少有一种可行方案数目,就是填入0,比如j=0,i=0时候我们可以填入0,所以我们可以执行赋值语句
dp[0][0][0] = 1;
第二种是min(zero,limit)=limit,这时候limit比zero至少小1,这时候我们可以把0到limit填充上“0”,所以我们这里可以赋值“1”(指代码中dp[i][0][0] = 1;
的1,不是逻辑上的那个1)代码如下:对于1和0这两种其实一样思路,所以代码相同。所以也进行赋值。
for (int i = 0; i <= Math.min(zero, limit); i++) {
dp[i][0][0] = 1;
}
for (int j = 0; j <= Math.min(one, limit); j++) {
dp[0][j][1] = 1;
}
- 我不理解为什么要加上i=0,如果仅是j=0且…很好理解,与上面第3点相反,所以无法构造可行方案。
- 这句话意思是我 dp0[i][j]的可行方案数目是可以通过 dp0[i-1][j]和dp1[i-1][j]得来的,因为不管是 dp0[i-1][j]还是dp1[i-1][j]都是放了i-1个0和j个1,再多放一个零都是[i][j],且正好是以0为结尾,所以正好可以得到dp0[i][j]。
- 因为dp1是以1为结束,此时我添加一个0肯定满足条件:子数组同时包含0和1,因为此时我添加一个0之后,会出现1和0挨着的情况,子数组的长度最短不会比2更短。
- 第一句话很好理解,“当 i≤limit 时,显然可以通过在” 这句话后面应该是 dp0 而不是 dp1,因为我i<=limit,那么i肯定比limit+1小至少1,所以我在添加了一个0之后,一共只有i个0,那么0的总个数都比limit+1小,肯定满足条件:子数组同时包含0和1。
- 第二句话,为什么是dp1[i-limit-i][j]呢,是因为dp1[i-limit-i][j]的可选情况数量为n的话,那么我还可以通过一种情况就是一直填上limit个0变成dp0[i-1][j]。