剑指 Offer II 011. 0 和 1 个数相同的子数组 Java解法

注明:本解法转载自https://leetcode.cn/u/liuduo-yu/ 用户在题目中的评论https://leetcode.cn/problems/contiguous-array/solution/lian-xu-shu-zu-by-leetcode-solution-mvnm/1212274
在这里插入图片描述
解释

/**
     * 解题思路:
     * 本题的意思是找到具有相同数量 0,1 的最长连续子数组,也就是子数组中要同时具有 0 和 1,并且 0 和 1 的数量是相同的, 并且是最长
     * 的子数组(好像是废话...)。
     * <p>
     * 给出一组测试用例:[0,0,0,1,1,1,0,0,1], 用指针 i 扫描一遍数组, 来观察每个位置上的可能情况
     * [0,0,0,1,1,1,0,0,1]
     * -i                 不符合条件, 0 1 数量不同
     * [0,0,0,1,1,1,0,0,1]
     * -  i               不符合条件, 0 1 数量不同
     * [0,0,0,1,1,1,0,0,1]
     * -    i             不符合条件, 0 1 数量不同
     * [0,0,0,1,1,1,0,0,1]
     * -      i           此时与前一个 0 构成 [0,1] 满足条件, 此时的子数组长度为 2
     * [0,0,0,1,1,1,0,0,1]
     * -        i         此时 [1,4] 区间满足条件, 子数组长度为 4
     * [0,0,0,1,1,1,0,0,1]
     * -          i       此时 [0,5] 区间满足条件, 子数组长度为 5
     * [0,0,0,1,1,1,0,0,1]
     * -            i     不符合条件
     * [0,0,0,1,1,1,0,0,1]
     * -              i   不符合条件
     * [0,0,0,1,1,1,0,0,1]
     * -                ^ 不符合条件
     * <p>
     * 当遍历完整个数组后, 我们可以知到 [0, 5] 区间是符合条件的最长连续子数组。肉眼很容易辨别哪个区间为最长连续子数组, 但是计算机如
     * 何能知道?答案是计算区间和, 如果让 0 变为 -1, 那么当区间内 -1 和 1 的数量相同时, 这区间和就是 0 。如此, 似乎可以使用前缀和
     * 来解决这个问题, 当计算的前缀和为 0 时, 就说明[0,i] 区间是满足题目要求的一个子数组。不过这样肯定会出现错误, 因为最终的结果不
     * 一定是从 0 下标开始子数组。例如这个用例 [0,0,1,0,0,0,1,1], 答案应该是 nums[2,7]区间长度为6的数组, 可以用上面的方式进行计算
     * 前缀和:
     * -[0,0,1,0,0,0,1,1]
     * - i               preSum = -1, (用 -1 替换 0);
     * -[0,0,1,0,0,0,1,1]
     * -   i             preSum = -2
     * -[0,0,1,0,0,0,1,1]
     * -     i           preSum = -1
     * -[0,0,1,0,0,0,1,1]
     * -       i         preSum = -2
     * -[0,0,1,0,0,0,1,1]
     * -         i       preSum = -3
     * -[0,0,1,0,0,0,1,1]
     * -           i     preSum = -4
     * -[0,0,1,0,0,0,1,1]
     * -             i   preSum = -3
     * -[0,0,1,0,0,0,1,1]
     * -               i preSum = -2
     * 观察可以发现, 当前缀和相同时, 前一个 i1 后面一个位置开始一直到 i2 的区间是满足题目要求的子数组, 即 nums[i1+1...i2] 满足题
     * 目要求, 并且 i2 - i1 = 子数组长度, 所以我们只需要计算出 nums[0...n-1] 每一个位置的前缀和, 一旦发现当前的计算出的前缀和在
     * 之前已经出现过, 就用当前的索引 i2 - 之前的索引 i1 即可求出本次得到的子数组长度,。因为需要求得的是最长连续子数组,所以应用一
     * 个变量 maxLength 来保存每一次计算出的子数组长度, 取较大值。也因为, 我们需要保存每一个位置的前缀和, 并且还需要通过前缀和找到
     * 相应位置的索引, 所以,使用 HashMap 来存放 {前缀和:索引}, 在上面例子中我们通过观察得到了 i2 - i1 = 数组长度, 但是有一个很隐
     * 蔽的缺陷, 即当整个数组即为答案时, i2 = nums.length - 1, i1 = 0 此时得到的数组长度为 nums.length - 1 这显然是错误的。因此
     * , 为了避免这个错误, 我们初始将 Map 中添加一对 {前缀和:索引}, 即 put(0,-1), 0代表前一个不存在的元素前缀和为 0, -1 代表不存
     * 在元素的索引。
     * 当定义了这些条件后, 我们开始用指针 i 遍历数组nums[0...nums.length - 1] 位置上的每一个元素。
     * 一、用变量 sum 来纪录[0...i]区间的和:
     * -   1.当 nums[i] == 0 时, sum += -1
     * -   2.当 nums[i] == 1 时, sum += 1
     * 二、接着判断 sum 是否已经存在于 HashMap 中:
     * -   1. 如果存在, 则取出 sum 所对应的索引 j, 那么 nums[j+1,i] 就是一个满足答案的子区间, 用
     * -      maxLength = Math.max(maxLengnth, i - j); 来纪录最长子数组。
     * -   2. 如果不存在, 则将 {sum:i} 存放在 HashMap 中作为纪录。
     * 当数组遍历完毕时, maxLength 中保存的即为答案数组的长度。
     * <p>
     */

程序

public int findMaxLength(int[] nums) {
        //key:前缀和 val:索引
        Map<Integer, Integer> map = new HashMap<>();
        //举例 当整个数组满足 0,1数量相同时 对应下标相减的结果 nums.length-1 - 0 = nums.length-1 并不是数组的长度
        //为了 解决这个问题 将map中加入(0,-1) 0为当前下标的前缀和 -1为当前下标
        map.put(0, -1);
        int ans = 0;
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            //sum表示前缀和
            sum += nums[i] == 0 ? -1 : nums[i];
            if (map.containsKey(sum)) {
                int j = map.get(sum);
                //i-j表示数组长度
                ans = Math.max(ans, i - j);
            } else {
                map.put(sum, i);
            }

        }
        return ans;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值