手摸手提桶跑路——LeetCode560.和为k的子数组

119 阅读2分钟

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

题目描述

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

示例 1:

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

示例 2:

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

提示:

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

解题思路——暴力法

我们初始化一个计数器 count,使用两层循环,外层循环的变量 i 负责控制起始下标,内层循环变量 j 负责游动下标,从 i 的位置开始,变量 j 一直累加到数组结束位置,如果累加结果等于给定的 k,则 count++

题解

/**
 * @param {number[]} nums
 * @param {number} k
 * @return {number}
 */
var subarraySum = function(nums, k) {
    let count = 0;
    for (let start = 0; start < nums.length; ++start) {
        let sum = 0;
        for (let end = start; end <nums.length; ++end) {
            sum += nums[end];
            if (sum == k) {
                count++;
            }
        }
    }
    return count;
};

c.png

多少有点惨不忍睹了,但是没关系,我们继续看看有什么别的好方法!

微信图片_20220727184909.jpg

解题思路——前缀和+hashmap

暴力的解法受限于我们每次求 [i, j] 中的和为 k 的子数组时,都要计算一次和,可以用 Map 集合来存储当前遍历元素前的所有元素的和来优化,这么做有什么好处呢?

QQ截图20220815151106.png

我们都知道,presum[i]nums[0] + nums[1] + … + nums[i-1] 的和,那么怎么找到和为 k 的子数组呢?当前前缀和 presum[i] 减去前面某个前缀和 presum[n]差值,如果等于 k 的话,是不是就说明被减去的前缀和到当前前缀和之间的元素的和等于 k?是不是有点绕,俺们看个图。

QQ截图20220815151652.png

当数组遍历到下标为 4 的元素时,当前前缀和为 12,去寻找前面的前缀和,发现存在一个前缀和为 12-k 的前缀和,说明这个剩下的区间就是和为 k 的数组了。

题解

var subarraySum = function(nums, k) {
    let totalSum = 0, count = 0;
    const mp = new Map();
    mp.set(0, 1);
    for(let i=0; i<nums.length; ++i) {
        totalSum += nums[i];
        if(mp.has(totalSum - k)) {
            count += mp.get(totalSum - k);
        }
        if(mp.has(totalSum)) {
            mp.set(totalSum, mp.get(totalSum) + 1);
        } else {
            mp.set(totalSum, 1);
        }
    }
    return count;
};

结束语

前缀和的思想经常用于求子串和和子数组和的题型上,当我们碰到这类题型,应该想到前缀和的解题方式,好好理解本文,日后对同类型的题型也能轻松应对。