GuilinDev

Lc0560

05 August 2008

560 - Subarray Sum Equals K

原题概述

Given an array of integers and an integer k, you need to find the total number of continuous subarrays whose sum equals to k.

Example 1:

1
2
Input:nums = [1,1,1], k = 2
Output: 2

Note:

  1. The length of the array is in range [1, 20,000].
  2. The range of numbers in the array is [-1000, 1000] and the range of the integer k is [-1e7, 1e7].

题意和分析

1) 这道题是找到所有的子序列的个数,这些子序列的元素的和等于给定的一个target。暴力解法子数组左右两个边界遍历O(n^2),嵌套加上求子数组的和O(n),总共O(n^3),不行。

2) 如果使用前缀和,可以先计算所有前缀的sum,可以降到两层循环,前缀和就是相当于固定了左边界,枚举右边界,或者固定右边界,枚举左边界,降了一维,O(n^2)。

3) 前缀和+哈希表优化做法:题目只关心子数组的个数,不关心具体的子数组长什么样子。定义preSum[i]为[0, i]之间元素的和,preSum[i]可以由preSum[i - 1]得到,即,preSum[i] = preSum[i - 1] + nums[i]:

preSum[i] - preSum[j] = nums[j + 1] + nums[j + 2] + …+ nums[i - 1] + nums[i] ?= k,简单移项转换成:

preSum[j] == preSum[i] - k

所以,统计有多少个preSum[i] - k等于preSum[j]这个条件的个数是我们需要的,于是把preSum[i] - k作为key,把前面计算的preSum[j]前面本身出现的次数作为value,如果再次出现(preSum[j] == preSum[i] - k),说明等式成立,[i, j]这个区间出现了和为k的子数组,结果+1。

对于一开始的情况,下标 0 之前没有元素,可以认为前缀和为 0,个数为 1 个,因此

1
preSumFreq.put(0, 1);
,这一点是必要且合理的。举例说明:

Array = {3,4,7,2,-3,1,4,2},k= 7,如果遇到二者相减(sum - k)等于7,或者sum本身等于7或者7的倍数,subarray的count均+1,(注意黑体字)

  • 循环初始map 为 {0, 1}, preSum = 0, result = 0;{0,1}为初始,preSum为0开始
  • 循环第一个遇到3,map 为 {0, 1}, {3, 1};preSum = 3;preSum - k = -4;result = 0;
  • 循环第二个遇到4,map 为 {0,1}, {3,1}, {7,1};preSum = 7;preSum - k = 0,0作为key之前出现过, 结果+1,result = 1;
  • 循环第三个遇到7,map 为 {0,1}, {3,1}, {7, 1}, {14, 1};preSum = 14;preSum - k = 7,7作为key之前出现过,结果+1, result = 2;
  • 循环第四个遇到2,map 为 {0,1}, {3,1}, {7,1}, {14,1}, {16,1};preSum = 16;preSum - k = 9,result = 2;
  • 循环第五个遇到-3,map 为 {0,1}, {3,1}, {7,1}, {14,1}, {16,1}, {13,1};preSum = 13;preSum - k = 6, result = 2;
  • 循环第六个遇到1,map 为 {0,1}, {3,1}, {7,1}, {14,2}, {16,1}, {13,1};preSum = 14;preSum - k = 7,7作为key之前出现过,结果+1, result = 3;
  • 循环第七个遇到4,map 为 {0,1}, {3,1}, {7,1}, {14,2}, {16,1}, {13,1}, {18,1};preSum = 18; preSum - k = 11;result = 3;
  • 循环第八个遇到2,map - {0,1}, {3,1}, {7,1}, {14,2}, {16,1}, {13,1}, {18,1}, {20,1},preSum = 20;preSum - k = 13,13作为key之前出现过,结果+1,result = 4;
  • 循环结束

如此,通过preSum[j] - preSum[i] = k的数目,计算出结果。Time:O(n);Space:O(n)。

代码

只用前缀和,O(n^2)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class Solution {
    public int subarraySum(int[] nums, int k) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int len = nums.length;
        int result = 0;
        for (int right = 0; right < len; right++) {
            int preSum = 0; // 每次遍历每个sum记录以固定右边界的前缀和
            for (int left = right; left >= 0; left--) {
                preSum += nums[left];
                if (preSum == k) {
                    result++;
                }
            }
        }
        return result;
    }
}

前缀和 + HashMap优化,O(n)的解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution {
    public int subarraySum(int[] nums, int k) {
        if (nums == null || nums.length == 0) {
            return 0;
        }
        int preSum = 0;
        int result = 0;
        HashMap<Integer, Integer> map = new HashMap<>();
        map.put(0, 1); // 初始化,表示达到target-t时的sum有一个
        
        for (int num : nums) {
            preSum += num;
            if (map.containsKey(preSum - k)) { // preSum[i] - preSum[j] = k移项
                result += map.get(preSum - k);
            }
            map.put(preSum, map.getOrDefault(preSum, 0) + 1);
        }
        return result;
    }
}