【LeetCode】第930题——和相同的二元子数组(难度:中等)

【LeetCode】第930题——和相同的二元子数组(难度:中等)

题目描述

给你一个二元数组 nums ,和一个整数 goal ,请你统计并返回有多少个和为 goal 的 非空 子数组。

子数组 是数组的一段连续部分。

  1. 示例 1:
    输入:nums = [1,0,1,0,1], goal = 2
    输出:4
    解释:
    如下面黑体所示,有 4 个满足题目要求的子数组:
    [1,0,1,0,1]
    [1,0,1,0,1]
    [1,0,1,0,1]
    [1,0,1,0,1]

  2. 示例 2:
    输入:nums = [0,0,0,0,0], goal = 0
    输出:15

提示:
1 <= nums.length <= 3 * 104
nums[i] 不是 0 就是 1
0 <= goal <= nums.length

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/binary-subarrays-with-sum
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

解题思路

思路一:哈希表+前缀和

利用哈希表存储<数组前缀和, 出现的频次>,如此一来当前缀和preSum已经大于等于goal的时候,计数需要加上hashmap.getOrDefault(preSum-goal, 0),(preSum-goal代表子数组的和)且如果preSum刚好等于goal,计数额外+1


思路二:滑窗

需要用到两个左指针left1和left2,一个右指针right,以及两个求和变量sum1和sum2。l e f t 1 ≤ l e f t 2 ≤ r i g h t left1 \leq left2 \leq rightleft1left2right

外层循环每次移动一次右指针,内层循环分为对left1的循环和对left2的循环

  • left1的循环为:left1每次右移一个,直到[left1, right]子数组的和小于goal
  • left2的循环为:left2每次右移一个,直到[left1, right]子数组的和小于等于goal

如此一来,left2-left1即为当前右指针right下,子数组和等于goal的个数,这个“个数”随着右指针的不断右移而不断累加

代码详解

思路一:哈希表+前缀和

class Solution {
    public int numSubarraysWithSum(int[] nums, int goal) {
        int preSum = 0; // 前缀和
        int count = 0; // 计数器
        Map<Integer, Integer> hm = new HashMap<>(); // 保存<前缀和, 出现的频次>
        for(int num : nums) {
            preSum += num;
            if(preSum >= goal) { // 在前缀和大于等于goal的情况下
                if(preSum == goal) { // 如果等于则计数器额外+1
                    ++count;
                }
                count += hm.getOrDefault(preSum - goal, 0); // 前缀和之差即为区间和
            }
            hm.put(preSum, hm.getOrDefault(preSum, 0) + 1); // 更新哈希表
        }
        return count;
    }
}

思路二:滑窗

class Solution {
    public int numSubarraysWithSum(int[] nums, int goal) {
        int count = 0; // 计数器
        int left1 = 0; // 左指针1,小于等于左指针2,小于等于右指针
        int left2 = 0; // 左指针2,小于等于右指针
        int right = 0; // 右指针
        int sum1 = 0; // 区间和1,对应[左指针1, 右指针]
        int sum2 = 0; // 区间和2,对应[左指针2, 右指针]
        for(int num : nums) {
            sum1 += num;
            while(left1 <= right && sum1 > goal) { // 注意条件是sum1大于goal
                sum1 -= nums[left1];
                ++left1; // 1号滑窗左端点右移,滑窗减小
            }
            sum2 += num;
            while(left2 <= right && sum2 >= goal) { // 注意条件是sum2大于等于goal
                sum2 -= nums[left2];
                ++left2; // 2号滑窗左端点右移,滑窗减小
            }
            // 1号滑窗与2号滑窗相差的地方就只在等于goal这种情况,因此left2-left1即为当前right指针下,子数组和等于goal的个数
            count += left2 - left1;
            ++right; // 滑窗右端点右移,滑窗扩大
        }
        return count;
    }
}

注意点

  • 针对子区间的问题常想到滑窗,而对于数字数组的子区间问题还可以考虑前缀和

版权声明:本文为qq_43536930原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。