leetcode 523、560—— 前缀和技巧

前缀和

1、定义

前缀和的思路是这样的,对于一个给定的数组 nums,我们额外开辟一个前缀和数组进行预处理。

int n = nums.size();
// 前缀和数组
vector<int>preSum(n + 1);
for (int i = 0; i < n; i++)
    preSum[i + 1] = preSum[i] + nums[i];

在这里插入图片描述
preSum[i] 就是 nums[0..i-1] 的和。那么如果我们想求 nums[i..j] 的和,只需要一步操作 preSum[j+1]-preSum[i] 即可,而不需要重新去遍历数组了。

2、题解:LeetCode560. 和为K的子数组

原题
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

输入:nums = [1,1,1], k = 2
输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。

说明 :

  • 数组的长度为 [1, 20,000]。
  • 数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。

方法一:穷举所有子数组,计算子数组的和

关键是,如何快速得到某个子数组的和呢,比如说给你一个数组 nums,让你实现一个接口 sum(i, j),这个接口要返回 nums[i..j] 的和,而且会被多次调用,你怎么实现这个接口呢?

因为接口要被多次调用,显然不能每次都去遍历 nums[i..j],有没有一种快速的方法在 O(1) 时间内算出 nums[i..j] 呢?这就需要前缀和技巧了。

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int len = nums.size();
        vector<int> arrsum(len + 1);
        for(int i = 0;i < len;i++)
        {
            arrsum[i+1] = arrsum[i] + nums[i];
        }
        int ans = 0;
        for(int i = 1;i <= len;i++)
        {
            for(int j = 0; j < i;j++)
            {
                if(arrsum[i] - arrsum[j] == k)
                    ans++;
            }
        }
        return ans;
    }
};

问题:时间复杂度太高:超时
在这里插入图片描述

优化解法

for(int i = 1;i <= len;i++)
        {
            for(int j = 0; j < i;j++)
            {
                if(arrsum[i] - arrsum[j] == k)
                    ans++;
            }
        }

第二层 for 循环在干嘛呢?

  • 有几个 j 能够使得 sum[i]sum[j]差为 k。毎找到一个这样的 j,就把结果加一。
arrsum[i] - arrsum[j] == k
arrsum[j] == arrsum[i] - k

优化的思路是:
直接记录下有几个 sum[j]sum[i] - k 相等,直接更新结果,就避免了内层的 for 循环。可以用哈希表,在记录前缀和的同时记录该前缀和出现的次数

class Solution {
public:
    int subarraySum(vector<int>& nums, int k) {
        int sum = 0, ans = 0;
        unordered_map<int,int> mp;
        mp[0] = 1;
        for(int i: nums){
            sum += i;
            if(mp.find(sum-k) != mp.end()) 
                ans += mp[sum-k];
            //把前缀和 nums[0..i] 加入并记录出现次数
            mp[sum] ++;
        }
        return ans;
    }
};

比如说下面这个情况,需要前缀和 8 就能找到和为 k 的子数组了,之前的暴力解法需要遍历数组去数有几个 8,而优化解法借助哈希表可以直接得知有几个前缀和为 8。
在这里插入图片描述

3、题解:LeetCode523. 连续的子数组和

原题链接
给定一个包含 非负数 的数组和一个目标 整数 k,编写一个函数来判断该数组是否含有连续的子数组,其大小至少为 2,且总和为 k 的倍数,即总和为 n*k,其中 n 也是一个整数。

示例 1:

输入:[23,2,4,6,7], k = 6
输出:True
解释:[2,4] 是一个大小为 2 的子数组,并且和为 6。
示例 2:

输入:[23,2,6,4,7], k = 6
输出:True
解释:[23,2,6,4,7]是大小为 5 的子数组,并且和为 42。
 

说明:

数组的长度不会超过 10,000 。
你可以认为所有数字总和在 32 位有符号整数范围内。
通过次数19,987提交次数89,540

class Solution {
public:
    int checkSubarraySum(vector<int>& nums, int k) {
        if (nums.empty()) {
            return false;
        }
        vector<int> pre_sum(nums.size() + 1, 0);
        for (int i = 0; i <nums.size(); ++i) {
            pre_sum[i + 1] = nums[i] + pre_sum[i];
        }

        for (int i = 1; i <= nums.size(); ++i) {
            for (int j = i; j <= nums.size(); ++j) {
                if (k == 0 && (pre_sum[j] - pre_sum[i - 1]) == 0 && j - i + 1 >= 2) {
                    return true;
                }
                else if (k != 0 && (pre_sum[j] - pre_sum[i - 1]) % k == 0 && j - i + 1 >= 2) {
                    return true;
                }
            }
        }
        return false;
    }
};

优化方案

1、空间换时间

map<int, int> mp;
key对应前缀和,value对应索引

2、公式转换

(arr[i] - arr[j])%k == 0

进一步得到

arr[i]%k  == arr[j]%k

3、程序结果

class Solution {
public:
// 转换为求sum=0的情况
    bool checkSubarraySum(vector<int>& nums, int k) {
        map<int, int> mp;
        mp[0] = -1;
        int sum = 0;
        for (int i = 0; i < nums.size(); i++) {
            sum += nums[i];
            if (k != 0) sum %= k;
            if (mp.find(sum) != mp.end()) {
                if (i - mp[sum] > 1) return true; 
            } else {
                mp[sum] = i;
            }
        }
        return false;
    }
};

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值