leetcode总结之前缀和应用

560. Subarray Sum Equals K

题目描述很简单,算出一共有几个和为 k 的子数组。

  1. 最简单的想法,固定一个位置,找从这个位置往后的所有连续子数组的和,如果和为k则结果加1,最后返回结果,复杂度O(N^2),数据量大时候会TLE。
class Solution {
public:
    int subarraySum(vector& nums, int k) {
        int result = 0;
        for (int i = 0; i < nums.size(); ++i) {
            int sum = 0;
            for (int j = i; j < nums.size(); ++j) {
                sum += nums[j];
                if (sum == k) result++;
            }
        }
        return result;
    }
};
  1. 那么如何快速得到某个子数组的和呢,比如一个数组 nums,让你实现一个接口 sum(i, j),这个接口要返回 nums[i..j] 的和,而且会被多次调用,你怎么实现这个接口呢?因为接口要被多次调用,显然不能每次都去遍历 nums[i..j],有没有一种快速的方法在 O(1) 时间内算出 nums[i..j] 呢?这就需要前缀和技巧了。我们新建一个前缀和数组 preSum,数组长度是nums数组长度加1,preSum[i] 表示 nums[0..i-1] 的和。那么如果我们想求 nums[i..j] 的和,只需要一步操作 preSum[j+1] - preSum[i] 即可,而不需要重新去遍历数组了,按照这个思路我们写一下代码如下:
class Solution {
public:
    int subarraySum(vector& nums, int k) {
        vector preSum(nums.size()+1, 0);
        for (int i = 0; i < nums.size(); ++i) {
            preSum[i+1] = preSum[i] + nums[i];
        }
        
        int result = 0;
        for (int i = 1; i < preSum.size(); ++i) {
            for (int j = i - 1; j >= 0; --j) {
                if (preSum[i] - preSum[j] == k) result++;
            }
        }
        return result;
    }
};

​ 很明显,这样子直接来求还是O(N^2)复杂度,但是我们了解了前缀和原理,对于直接求i...j的和可以以O(1)方式提供。

  1. 我们来继续优化,上面2解法的里层循环是找到前面的位置,是否有和为(preSum[i]-k)的位置,我们可以通过空间换时间的方法,建立一个额外的map来存储每个和对应的数量,这样子在遍历到某个位置时候,前面所有的和以及出现的次数都已经记录了。哈希表初始化{0, 1}的原因:我们建立哈希表的目的是为了让我们可以快速的查找 sum-k 是否存在,即是否有连续子数组的和为 sum-k,如果存在的话,那么和为k的子数组一定也存在,这样当 sum 刚好为k的时候,那么数组从起始到当前位置的这段子数组的和就是k,满足题意,如果哈希表中事先没有 m[0] 项的话,这个符合题意的结果就无法累加到结果 res 中,这就是初始化的用途。
class Solution {
public:
    int subarraySum(vector& nums, int k) {
        vector preSum(nums.size()+1, 0);
        for (int i = 0; i < nums.size(); ++i) {
            preSum[i+1] = preSum[i] + nums[i];
        }
        
        int result = 0;
        unordered_map mmap;
        for (int i = 0; i < preSum.size(); ++i) {
            result += mmap[preSum[i] - k];
            mmap[preSum[i]]++;
        }
        return result;
    }
};

​ 这样子时间复杂度就是O(N),空间复杂度O(N),当然上述代码还可以继续优化去掉preSum数组,用原数组来替代,代码如下:

class Solution {
public:
    int subarraySum(vector& nums, int k) {
        int result = 0;
        unordered_map mmap = {{0, 1}};
        for (int i = 1; i < nums.size(); ++i) nums[i] += nums[i-1];
        for (int i = 0; i < nums.size(); ++i) {
            result += mmap[nums[i] - k];
            mmap[nums[i]]++;
        }
        return result;
    }
};

523. Continuous Subarray Sum

题目分析:题目要求判断是否有连续子数组的和是K的N倍<数组中数都是non-negative的>,K有可能是0,由于是K的N倍,我们首先知道(a - b) % k = a % k - b % k,要想使(a - b) % k == 0,只需要满足a % k - b % k == 0即可,我们可以计算前缀和,同时将前缀和对K的取余记录到一个map中,这样子我们一遍遍历到某个位置,前面已经记录了任意位置和对k取余的的值,只要满足当前位置的和对k取余的值在之前存在过,而且距离大于等于2,都满足题目要求。

class Solution {
public:
    bool checkSubarraySum(vector& nums, int k) {
        unordered_map mmap = {{0, -1}};
        for (int i = 1; i < nums.size(); ++i) nums[i] += nums[i-1];
        for (int i = 0; i < nums.size(); ++i) {
            int mod = k == 0 ? nums[i] : nums[i] % k; // k的N倍,如果k是0,和为0时候也是k的N倍
            if (mmap.count(mod)) {
                if (i - mmap[mod] >= 2) return true;
            } else {
                mmap[mod] = i;
            }
        }
        return false;
    }
};

974. Subarray Sums Divisible by K

题目分析:本题目是前面两个题目的结合体,要找出所有能被K整除的子数组和的个数,改题目中由于可能有负数,需要注意一下,取余为负数时候需要进行+K操作得到正数的取余。

class Solution {
public:
    int subarraysDivByK(vector& A, int K) {
        int result = 0;
        unordered_map mmap = {{0, 1}};
        for (int i = 1; i < A.size(); ++i) A[i] += A[i-1];
        for (int i = 0; i < A.size(); ++i) {
            int mod = A[i] % K;
            if (mod < 0) mod += K;
            result += mmap[mod];
            mmap[mod]++;
        }
        return result;
    }
};

参考

  1. https://www.cnblogs.com/grandyang/p/6810361.html
  2. https://zhuanlan.zhihu.com/p/107778275

你可能感兴趣的:(leetcode,leetcode)