给定一个二进制数组 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 <= 105nums[i]不是0就是1
解题思路
这道题如果用暴力解法的话,就是枚举所有的情况进行判断最大长度,但显然我们不会这么做的!
其实这道题要想快速求解的话,我们需要转换一下题意,这个还是比较难想到的!因为这道题的数组中只有 0 和 1,那么只要将数组中的 0 都转化为 -1,就变成了我们去求数组中和为 0 的一段连续子数组,即变成了 560. 和为 K 的子数组 这道题的特殊情况了!只不过有一些细节问题,因为这道题要求的是长度,而不是个数,所以我们要稍微修改一些细节!
细节修改如下所示:
- 哈希表中存放什么❓❓❓
- 首先哈希表的第一个元素,肯定存放的还是前缀和的大小,而第二个元素就不一样了,这次要记录的是前缀和的长度,但是我们可以在遍历的时候再进行计算,所以这里第二个元素我们选择存放此时的下标!
- 什么时候存入哈希表❓❓❓
- 这个和 560. 和为 K 的子数组 这道题是一样的,我们需要边走边存入哈希表,这样子才不会被后面的重复键值对影响,但其实这道题由于我们要存放的键值对是第一对(下面会解释),所以我们是可以提前将数组预处理放入到哈希表中的,但是这里我们就统一还是边走边存入哈希表的方式遍历!
- 如果有重复的
<sum, i>键值对,该存哪一对❓❓❓- 因为这道题要求的是最长的连续子数组长度,但是我们存入哈希表中的是前缀和,那么肯定是前缀和长度越短,后面的连续子数组长度就越长,所以我们要存放的是第一次出现的前缀和的下标!
- 之前所说的前缀和为
0的特殊情况,如何处理❓❓❓- 前缀和为
0的情况,说明[0, i]区间元素之和就是0,此时我们必须让hash[0] = -1,因为如果让hash[0] = 0或者等于1的话都是不符合的,因为我们最后要计算长度,通过i - hash[0]的长度来获得当前的最大长度,那么如果此时hash[0] = -1,说明i - (-1)其实得到的就是从[0, i]区间长度!
- 前缀和为
- 最后的连续子数组长度如何计算❓❓❓
- 长度的计算其实很简单,因为我们此时遍历到了
i,并且我们还知道前面存在一个满足要求的前缀和,那我们只需要通过i - hash[sum - k]就能得到此时的长度,因为哈希表第二个元素表示的是下标,而因为这道题是 560. 和为 K 的子数组 这道题的特殊情况,即k = 0,则最后就能得到长度是通过i - hash[sum]得到的!
- 长度的计算其实很简单,因为我们此时遍历到了
class Solution {
public:
int findMaxLength(vector<int>& nums) {
// 将数组中0转化为-1
for(int i = 0; i < nums.size(); ++i)
if(nums[i] == 0)
nums[i] = -1;
// 剩下的问题就相当于转化为了求和为0的连续子数组(之前做过)
unordered_map<int, int> hash;
hash[0] = -1; // 特殊情况
int sum = 0, len = 0;
for(int i = 0; i < nums.size(); ++i)
{
sum += nums[i]; // 边走边累加前缀和
if(hash.count(sum - 0))
len = max(len, i - hash[sum - 0]);
else
hash[sum] = i; // 只有第一次出现的键值对才进行哈希表的插入
}
return len;
}
};