给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。
题解
涉及到连续子数组的,可以考虑滑动窗口或者前缀和(+哈希表)
方法:前缀和+哈希表
在本方法中,我们可以将数组中的 0 变成 −1,则问题就变成了找到和为0的最长连续子数组,并返回该子数组的最大长度。
思路和算法
本题只需要求出满足要求的子数组的最大长度就行了,并不需要求出具体的子数组。
我们可以使用right指针从左到右扫描一遍数组,将数组中的 0 变成 −1,同时计算当前前缀和prefix[right] (子数组nums[0]…nums[right]之和),然后使用哈希表来存储每个前缀和第一次出现时的末尾元素的下标。
这时我们需要寻找符合条件的子数组,具体思路如下:
通过哈希表找出等于当前前缀和prefix[right]的子数组的下标i。
如果寻找到了这样的子数组,就说明我们寻找到了nums[0]…nums[i]的连续子数组,其之和为prefix[i],其中prefix[i]等于prefix[right],并且i<=right。
我们就可以通过prefix[right] - prefix[i]来得出nums[i+1]…nums[right]子数组之和为0,说明找到了符合新问题2中的条件的子数组。
其子数组的长度为right-i。
另外之所以存储每个前缀和第一次出现的下标是因为我们需要找出满足要求的数组的最大长度,所以下标越小越好。
我们只需要从左到右扫描数组一次,prefix可以实时地去计算,不需要使用数组来保存。
其中哈希表在初始化的时候需要插入{0, -1},目的是为了可以求出符合题中条件的起始元素在下标为0时的连续子数组的情况。
class Solution {
public int findMaxLength(int[] nums) {
HashMap<Integer,Integer> map = new HashMap<>();
int sum=0;
int ans=0;
int len = nums.length;
//为了方便计算从0开始的sum
map.put(0,-1);
for(int i=0;i<len;i++){
if(nums[i]==0){
sum-=1;
}else{
sum+=1;
}
//因为是计算最长连续子数组,所以计算该sum值第一次出现的位置
if(!map.containsKey(sum)){
map.put(sum,i);
}else{
//子数组范围从i到i-map.get(sum)
ans=Math.max(ans,i-map.get(sum));
}
}
return ans;
}
}
你问我答
- 为什么在哈希表中找到了相同的 cur 值则算找到了一串连续数组?
“如果这是一串连续子数组,那么cur的值,在到达该子数组尾部时(紫色箭头处),与在该子数组前一位时(绿色箭头处),是相等的”
- 为什么要在哈希表中插入{0, -1}?
这是为了辅助讨论该连续数组的起始点在 index == 0 的位置的情况,如果最长连续数组在数组的最前方,不插入{0,-1}会得到错误的答案,因此我们一定要插入该辅助键值!具体可以看看动图中的前几位数字看看{0,-1}是如何辅助我们得到答案的!