leetcode hot100之和为 K 的子数组(560)解析

128 阅读4分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第18天,点击查看活动详情


前言

  • leetcode hot100,是大厂面试高频题,也是必刷算法题。精选了100道LeetCode上最热门的题目,适合初识算法与数据结构的新手和想要在短时间内高效提升的人,按照官方说的,熟练掌握这 100 道题,就具备了代码世界通行的基本能力。

leetcode560题(和为 K 的子数组)

本文来讲hot100第560题,和为 K 的子数组,本题题目虽然是中等题目,描述也很短,不过通过率是比较低的,很容易用错方法,走入误区。需要仔细分析题目条件。

给你一个整数数组 nums 和一个整数 k ,请你统计并返回 *该数组中和为 k ***的连续子数组的个数

示例:

输入: nums = [1,1,1], k = 2
输出: 2
输入: nums = [1,2,3], k = 3
输出: 2

提示(这里的第二个条件很重要):

  • 1 <= nums.length <= 2 * 104
  • -1000 <= nums[i] <= 1000
  • -107 <= k <= 107

分析

  1. 整数数组,那么有可能每个数是有重复的,每个数也是无序的。
  2. 和为k的连续子数组,一听到连续子数组,我们就会想到双指针、滑动窗口等方法。

思路

  • 如果是暴力法解决,可以直接使用三层循环,不断的去迭代查找,但是不用想也知道,肯定是超时的,所以这种行不通。
  • 忽略暴力法,看到连续子数组,就想直接用双指针/滑动窗口方法。(错误思路示范)
    1. 想到了数组可能是无序的,所以我们把数组先升序排列,小的放前面,大的放后面
    2. 设置快慢指针,慢指针在左,快指针在右,然后不断迭代,并累积我们截取的值, 此时会出现三种情况
      • 当我们截取的数组之和小于k,说明还不满足题目条件,快指针继续向右执行
      • 当我们截取的数组之和等于k,说明满足题目条件,但我们还需要快指针继续右迭代,因为可能存在重复
      • 当我们截取的数组之和大于k,说明超出条件,我们慢指针向右走,快指针跟在慢指针右边
    3. 这是正常的双指针方法,但是最致命的问题,我们为了解决可能为负值的问题,光想着用排序了,但是忽略了连续子数组这个问题,所以这种思路也不行,我们需要再换种想法。
  • 前缀和解法
    1. 前缀和,从第0项到当前位置的数(nums[i])的和,公式如下
      • nums[0] + nums[1] + ...+ nums[i]
    2. 题目要求连续子数组的和为k, 那么和为k的连续子数组的公式如下
      • nums[0] + nums[1] + ...+ nums[i] = k
    3. 那么 我们把k移到等号左边,公式如下
      • nums[0] + nums[1] +...+ nums[i] - k = 0
    4. 由此可以看出来前缀和与上面的公式结果的前部分一样
      • [前缀和] - k = 0
    5. 可以看出,每次只要算出前缀和,我们就可以拿到最终的结果的个数了

代码

方式一:先计算前缀和(不算当前的值),再更新map,最后查看和(累加到当前值 - k)相等的i之前的前缀和有多少个

let res = 0;
let map = new Map();
// 保存当前位置的前缀和
let sum = 0;
// 前缀和存储到map中
map.set(0, 1);
for (let i = 0; i < nums.length; i++){
    //因为第一个前缀和已处理,所以跳过第一个
    if (i !== 0){
        sum += nums[i - 1];
        map.set(sum, (map.get(sum) || 0) + 1);
    }
    // 查看和(累加到当前值 - k)相等的i之前的前缀的个数
    res += map.get(sum + nums[i] - k)) || 0
}
return res;

方式二:先计算前缀和(算当前的值),再进行统计,最后更新map更新

let res = 0;
let map = new Map();
// 保存当前位置的前缀和
let sum = 0;
// 前缀和存储到map中
map.set(0, 1);
for (let i = 0; i < nums.length; i++) {
    sum += nums[i]
    if (map.get(sum - k)) {
      res += map.get(sum - k)
    }
    map.set(sum, (map.get(sum) || 0) + 1)
  }
return res;

结语

  • 本题虽然是中等难度,但是通过率还是比较低的,刚看题目很容易想到双指针/滑动窗口,其实是不适合用的,因为nums[i]可以小于0,也就是说右指针i向后移1位不能保证区间会增大,左指针j向后移1位也不能保证区间和会减小。给定ji的位置没有二段性,所以我们需要在这个地方注意一下。
  • 本题主要是前缀和的应用,时间复杂度O(n)空间复杂度 O(n)