题目说数组长度最长可能到50000,所以如果暴力枚举子数组的起点、终点,再计算数组和,复杂度就是o(n^3),肯定超时。
可以预处理出前缀和,这样只需要枚举起点和终点即可,但时间复杂度依然是O(n^2), 也不行。
这里需要一点奇技淫巧,因为数组只包含有0和1,如果一段子数组含有相同数量的0和1,则这个子数组中一半是0,一半是1,那么子数组的和就是这个子数组的长度的一半。
更进一步,如果我们把数组中所有的0都当作-1处理,那么,当一个子数组中含有相同的0和1(也就是含有相同数量的-1和1)时,我们计算出来的子数组的和就是0了。
所以,我们可以用一个哈希表存取所有前缀和的下标,这里的前缀和不是直接对原数组计算前缀和,而是把数组中所有0按照-1处理、1仍然按1处理所得到的前缀和。
哈希表中记录当前前缀和的下标,因为题目要求最长的连续子数组,所以我们只记录当前前缀和的最小的下标,如果之后碰到了一个和当前前缀和相同的前缀和,则这两个下标的差,就是一个含有相同的0和1的子数组的长度,可以用这个长度更新答案。
上面这几句话有点抽象,实际上就是,假设我们当前得到了一个前缀和x,如果哈希表中没有记录过这个前缀和对应的下标,那么我们在哈希表中记录前缀和x对应的下标(假设为i);之后,如果我们又得到了一个前缀和preSum[j]也是x,则preSum[j] - preSum[i - 1] = 0, 也就是说子数组nums[i ~ j]中含有相同的0和1,因为把所有的0当作-1进行相加之后,得到的子数组的和为0。
既然得到了满足条件的一个子数组,我们可以用这个子数组的长度更新答案:res = max(res, i - hash[CurPreSum]。
这样,通过用一个哈希表记录前缀和的下标(这里的前缀和计算把原数组中所有的0按照-1处理),我们可以在O(n)的时间复杂度内计算出含有相同数量的0和1的最长连续子数组的长度。
代码如下:
class Solution {
public:
unordered_map<int, int> hash; // 哈希表记录某个前缀和所对应的(最小)下标
int findMaxLength(vector<int>& nums) {
hash[0] = 0;
int res = 0;
int curPreSum = 0; // 当前的前缀和
for(int i = 1; i <= nums.size(); ++i) {
curPreSum += (nums[i - 1] == 1) ? 1 : -1; // 0按照-1处理,1仍然是1
if(hash.find(curPreSum) == hash.end()) { // 如果之前没有记录过curPreSum的下标,则记录一下
hash[curPreSum] = i;
} else {
res = max(res, i - hash[curPreSum]); // 否则,说明找到了一段满足条件的子数组,用这个子数组的长度更新答案
}
}
return res;
}
};