让3岁小孩都能理解LeetCode每日一题_3129.找出所有稳定的二进制数组 I

在这里插入图片描述

在这里插入图片描述

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);
    }
}

动态规划问题将会在我的另外一篇博客中详细讲解。
这里对官方答案进行解释:我们首先要知道,我们不是把所有数组生成了才去检测每个数组的子数组,而是再满足子数组条件的前提下,我们慢慢生成整个数组。

  1. 题目要求二进制数组 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]。
  2. 按照题目要求,我们需要将 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通过同理可得。

在这里插入图片描述

  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;
}

在这里插入图片描述

  1. 我不理解为什么要加上i=0,如果仅是j=0且…很好理解,与上面第3点相反,所以无法构造可行方案。

在这里插入图片描述

  1. 这句话意思是我 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]。
    在这里插入图片描述
  2. 因为dp1是以1为结束,此时我添加一个0肯定满足条件:子数组同时包含0和1,因为此时我添加一个0之后,会出现1和0挨着的情况,子数组的长度最短不会比2更短。
    在这里插入图片描述
  3. 第一句话很好理解,“当 i≤limit 时,显然可以通过在” 这句话后面应该是 dp0 而不是 dp1,因为我i<=limit,那么i肯定比limit+1小至少1,所以我在添加了一个0之后,一共只有i个0,那么0的总个数都比limit+1小,肯定满足条件:子数组同时包含0和1。
  4. 第二句话,为什么是dp1[i-limit-i][j]呢,是因为dp1[i-limit-i][j]的可选情况数量为n的话,那么我还可以通过一种情况就是一直填上limit个0变成dp0[i-1][j]。
  • 5
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
给定一个整数数组 nums 和一个目标值 target,要求在数组找出两个数的和等于目标值,并返回这两个数的索引。 思路1:暴力法 最简单的思路是使用两层循环遍历数组的所有组合,判断两个数的和是否等于目标值。如果等于目标值,则返回这两个数的索引。 此方法的时间复杂度为O(n^2),空间复杂度为O(1)。 思路2:哈希表 为了优化时间复杂度,可以使用哈希表来存储数组中的元素和对应的索引。遍历数组,对于每个元素nums[i],我们可以通过计算target - nums[i]的值,查找哈希表中是否存在这个差值。 如果存在,则说明找到了两个数的和等于目标值,返回它们的索引。如果不存在,将当前元素nums[i]和它的索引存入哈希表中。 此方法的时间复杂度为O(n),空间复杂度为O(n)。 思路3:双指针 如果数组已经排序,可以使用双指针的方法来求解。假设数组从小到大排序,定义左指针left指向数组的第一个元素,右指针right指向数组的最后一个元素。 如果当前两个指针指向的数的和等于目标值,则返回它们的索引。如果和小于目标值,则将左指针右移一位,使得和增大;如果和大于目标值,则将右指针左移一位,使得和减小。 继续移动指针,直到找到两个数的和等于目标值或者左指针超过了右指针。 此方法的时间复杂度为O(nlogn),空间复杂度为O(1)。 以上三种方法都可以解决问题,选择合适的方法取决于具体的应用场景和要求。如果数组规模较小并且不需要考虑额外的空间使用,则暴力法是最简单的方法。如果数组较大或者需要优化时间复杂度,则哈希表或双指针方法更合适。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值