LeetCode560之和为K的子数组(相关话题:前缀和)

题目描述

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

示例 1 :

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

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

思路分析

这个前缀和数组preSum的含义也很好理解,preSum[i]就是nums[0..i-1]的和。那么如果我们想求nums[i..j]的和,只需要一步操作preSum[j+1]-preSum[i]即可,而不需要重新去遍历数组了。

int subarraySum(int[] nums, int k) {
    int n = nums.length;
    // 构造前缀和
    int[] sum = new int[n + 1];
    sum[0] = 0; 
    for (int i = 0; i < n; i++)
        sum[i + 1] = sum[i] + nums[i];

    int ans = 0;
    // 穷举所有子数组
    for (int i = 1; i <= n; i++)
        for (int j = 0; j < i; j++)
            // sum of nums[j..i-1]
            if (sum[i] - sum[j] == k)
                ans++;

    return ans;
}

这个解法的时间复杂度图片空间复杂度图片,并不是最优的解法。不过通过这个解法理解了前缀和数组的工作原理之后,可以使用一些巧妙的办法把时间复杂度进一步降低。

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

代码实现

    public int subarraySum(int[] nums, int k) {
        
        //key是前缀和,前缀和出现的次数
        Map<Integer,Integer> map = new HashMap(); 
        int sum_i = 0;
        int sum_j = 0;
        int result = 0;
        map.put(0,1);
        for(int i=0;i<nums.length;i++){

            sum_i = sum_i + nums[i];
            //和当前前缀和sum_i差k的前缀和为sum_j
            sum_j = sum_i- k;
            if(map.get(sum_j)!=null){
                //之前存在这个前缀和就把结果加map.get(sum_j)
                result = result + map.get(sum_j);
            }
             map.put(sum_i,map.getOrDefault(sum_i,0)+1);

        }

      return result;

  }

相关题目

LeetCode525连续数组:给定一个二进制数组 nums , 找到含有相同数量的 0 和 1 的最长连续子数组,并返回该子数组的长度。

示例 1:

输入: nums = [0,1]
输出: 2
说明: [0, 1] 是具有相同数量 0 和 1 的最长连续子数组。
示例 2:

输入: nums = [0,1,0]
输出: 2
说明: [0, 1] (或 [1, 0]) 是具有相同数量0和1的最长连续子数组。
 

提示:

1 <= nums.length <= 105
nums[i] 不是 0 就是 1

思路:由于「0 和 1 的数量相同」等价于「1 的数量减去 0 的数量等于 0」,我们可以将数组中的 0 视作 -1,则原问题转换成「求最长的连续子数组,其元素和为 0」。

    public int findMaxLength(int[] nums) {

        int n = nums.length;
        for (int i = 0; i < n; i++){
            if(nums[i]==0){
                nums[i]=-1;
            }
        }
        
        
            int max = 0;
            //key是前缀和,value是对应的下标
            Map<Integer,Integer> map = new HashMap(); 
            int sum_i = 0;
            int sum_j = 0;
            int result = 0;
            //"下标-1"处的前缀和为0(这个初始值很重要否则无法通[0,1]这个测试用例)
            map.put(0,-1);
            for(int i=0;i<nums.length;i++){
    
                sum_i = sum_i + nums[i];
                //和当前前缀和sum_i差0的前缀和为sum_j
                sum_j =  sum_i;
                if(map.get(sum_j)!=null){
                  //之前存在这个前缀和就计算数组长度,更新max
                    max = Math.max(max,i-map.get(sum_j));
                }else{
                    map.put(sum_i,i);
                }
 
            }
        return max;
    }

博主总结

要多刷同类题目才能把一种解题技巧灵活应用,深入体会。思路必须很清晰再写代码才可行(做任何事其实都是这种套路),同时要注意一些边界技巧的积累。

参考文章

前缀和技巧:解决子数组问题

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

数据与后端架构提升之路

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值