题目:
给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。
示例 1:
输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。
示例 2:
输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量 0 和 1 的最长连续子数组。
提示:
1 <= nums.length <= 105
nums[i] 不是 0 就是 1
分析:
拆解关键词:
【二进制数组、元素非0即1、0-1数量相同、连续子数组、最长、返回长度】
想法:
1、暴力破解法
2、前缀和
3、滑动窗口法
解释:
【暴力破解法】
暴力破解法的原理是将全部的子数组组合遍历一遍,然后从中找出最佳结果。暴力破解的依据原理在这里:欢迎移步暴力破解原理
这道题依旧是定义双层循环,i
,j
分别循环。题目要求0
和1
的个数相同的最长连续子数组,所以如果一个子数组是解,那么满足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
必须是偶数,如果不是偶数直接跳过【只有偶数才可以保证0
和1
个数相同】。根据这个条件可以运用前缀和求解 - 但是上述思路,我发现如果当前循环到下标=i的前缀和
sums[i]
,那么还需要去遍历j,判断是否符合解的条件,这样导致我的实现好像又变成暴力破解了😢 有点麻烦,希望大佬可以帮指点下~~不胜感激
第二个思路,我摊牌了😂 ,是参考官方题解的。官方yyds🐂🍺~
-
思路如下,解中要求
0
和1
的个数相同,那么可以将0
转化为-1
,这样一来,就变成寻找-1
和1
的个数相同,既然这俩个数相同了,那么全部的-1
和全部的1
加起来自然就是0
了。 -
题目成功的变成–>寻找和为
0
的最长子数组–>转化为数组公式—>sums[i]-sums[j]=0
,代表从i
开始到j
这一段数组,-1
和1
的个数是相同的【前提是0
转化为-1
了哈】就是满足题目的解。 -
如果当前存在多个
sums[j]
和sums[i]
一致,假设为sums[j1]
==sums[i]
,sums[j2]
=sums[i]
. -
也就是代表从
j1
到i
满足0
和1
个数相同,从j2
到i
也满足0
和1
个数相同。这个时候取哪一个呢?肯定是取j1
和j2
中下标位置较小的那个,因为这样才是满足条件的最长子数组。 -
我们可以使用
map
结构,来记录sums[j]
出现的首位下标,每次当sums[j]
=sums[i]
,就可以寻找sums[j]
的最早出现位置,i-j+1
以当前i
为结束位置的最长的解。 -
当
i
遍历结束,返回过程中寻找到的最长的长度。
【滑动窗口法】
滑动窗口需要确定的边界移动属性,但是此题中,窗口中right
指针无法确认是否该右移,因为右移永远可能寻找到下一个解,这个时候滑动窗口就无用武之地了。
如果这道题修改为找到0
和1
个数相同的最小连续子数组,这个时候就可以用滑动来做,因为right
指针有着明确的移动条件,继续后移将没有答案或者后续即使找到解也肯定不是答案。这种情况才可以用滑动~
代码:
第一版:暴力破解
暴力破解超时了(⊙o⊙)…😓
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;
}
}
第二版:前缀和
第一次将近双百😢😢😢
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
大家好,我是二十一画,感谢您的品读,如有帮助,不胜荣幸~😊