0和1个数相同的子数组 | 循序递进---@二十一画

题目:

给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。

示例 1:

输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量 01 的最长连续子数组。
示例 2:

输入: nums = [0,1,0]
输出: 2
说明: [0, 1] ([1, 0]) 是具有相同数量 01 的最长连续子数组。

提示:

1 <= nums.length <= 105
nums[i] 不是 0 就是 1

分析:

拆解关键词:

【二进制数组、元素非0即1、0-1数量相同、连续子数组、最长、返回长度】

想法:

1、暴力破解法

2、前缀和

3、滑动窗口法

解释:
【暴力破解法】

暴力破解法的原理是将全部的子数组组合遍历一遍,然后从中找出最佳结果。暴力破解的依据原理在这里:欢迎移步暴力破解原理

这道题依旧是定义双层循环,ij分别循环。题目要求01的个数相同的最长连续子数组,所以如果一个子数组是解,那么满足0的个数+1的个数=子数组的长度===>可得:当前子数组中的和一定等于 数组长度/2`

举例:满足条件的子数组【1,0,1,0,0,1】子数组的和=3 ,子数组的长度=6,满足2*3 = 6,依赖这个我们就可以将本道题也归纳为满足题目和的连续子数组。

之后使用暴力破解,寻求该条件成立即可作为解,遍历全部结果找到最大解返回

【前缀和】

先说我的第一个思路吧,这个思路我没想出来合适的方法达到O(n)的效率,所以先搁这里描述下:如果有大佬按照这个思路实现了O(n)的解法,可以提供下思路谢谢😊~~

  • 既然在暴力破解法描述的时候,已经发现了本题可以使用和与数组长度的关系来求解,那么使用前缀和的方法肯定是可以做的,如果有解,那么一定满足 sums[i]-sums[j] = (i-j)/2.这里特别强调,i-j+1必须是偶数,如果不是偶数直接跳过【只有偶数才可以保证01个数相同】。根据这个条件可以运用前缀和求解
  • 但是上述思路,我发现如果当前循环到下标=i的前缀和sums[i],那么还需要去遍历j,判断是否符合解的条件,这样导致我的实现好像又变成暴力破解了😢 有点麻烦,希望大佬可以帮指点下~~不胜感激

第二个思路,我摊牌了😂 ,是参考官方题解的。官方yyds🐂🍺~

  • 思路如下,解中要求01的个数相同,那么可以将0转化为-1,这样一来,就变成寻找-11的个数相同,既然这俩个数相同了,那么全部的-1和全部的1加起来自然就是0了。

  • 题目成功的变成–>寻找和为0的最长子数组–>转化为数组公式—>sums[i]-sums[j]=0 ,代表从i开始到j这一段数组,-11的个数是相同的【前提是0转化为-1了哈】就是满足题目的解。

  • 如果当前存在多个sums[j]sums[i]一致,假设为sums[j1]==sums[i],sums[j2]=sums[i].

  • 也就是代表从j1i满足01个数相同,从j2i也满足01个数相同。这个时候取哪一个呢?肯定是取j1j2中下标位置较小的那个,因为这样才是满足条件的最长子数组。

  • 我们可以使用map结构,来记录sums[j]出现的首位下标,每次当sums[j]=sums[i],就可以寻找sums[j]的最早出现位置,i-j+1以当前i为结束位置的最长的解。

  • i遍历结束,返回过程中寻找到的最长的长度。

【滑动窗口法】

滑动窗口需要确定的边界移动属性,但是此题中,窗口中right指针无法确认是否该右移,因为右移永远可能寻找到下一个解,这个时候滑动窗口就无用武之地了。

如果这道题修改为找到01个数相同的最小连续子数组,这个时候就可以用滑动来做,因为right指针有着明确的移动条件,继续后移将没有答案或者后续即使找到解也肯定不是答案。这种情况才可以用滑动~

代码:

第一版:暴力破解

暴力破解超时了(⊙o⊙)…😓

image.png

class Solution {
    public int findMaxLength(int[] nums) {

        return first01(nums);
    }

    public static int first01(int[] nums){

        int len = nums.length;
        int res_cnt = 0;
        int sum = 0;

        for(int i=0;i<len;i++){

            sum = 0;
						//因为解一定是偶数,所以这里遍历的时候j+2
            for(int j=i+1;j<len;j+=2){ 

                sum += (nums[j] + nums[j-1]);

                if((j-i+1)%2==0){
                    //判断是否满足条件
                    if(sum==((j-i+1)>>1)){
                        //满足更新
                        res_cnt = (j-i+1)>res_cnt?(j-i+1):res_cnt;
                    }
                }
            }
        }
        return res_cnt;
    }
}
第二版:前缀和

第一次将近双百😢😢😢

image.png

class Solution {
    public int findMaxLength(int[] nums) {

        return first02(nums);
    }

    public static int first02(int[] nums){

        int len = nums.length;
        int res_cnt = 0;
        int sum = 0;
        HashMap<Integer,Integer> map = new HashMap<>();

        for(int i=0;i<len;i++){

            sum += (nums[i]==1)?1:-1; //0转-1进行累加前缀和

            //如果此刻sum等于0,相当于从index=0到目前为止 加和为0,这个直接更新解
            if(sum==0){
                res_cnt = (i-0+1)>res_cnt?(i-0+1):res_cnt;
            }

            int firstPos = map.getOrDefault(sum,-1);//查询map中是否存在 sum的值  此时返回firstPos是一个最早出现sum值的下标位置
            if(firstPos!=-1){
                res_cnt = (i-firstPos)>res_cnt?(i-firstPos):res_cnt;//如果当前新的解长度大于之前的解,那么更新解
            }else{
                //如果当前的sum不存在 那么加入map
                map.put(sum,i);
            }
        }
        return res_cnt;
    }
}

总结:

前缀和yyds

大家好,我是二十一画,感谢您的品读,如有帮助,不胜荣幸~😊

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值