LeetCode525. 连续数组

题目说数组长度最长可能到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;
    }
};
posted @ 2020-11-14 12:14  machine_gun_lin  阅读(91)  评论(0编辑  收藏  举报