和为K的子数组 | 循序递进---@二十一画

题目:

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

示例 1 :

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

输出: 2
解释: 此题 [1,1][1,1] 为两种不同的情况
示例 2 :

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

提示:

1 <= nums.length <= 2 * 10^4
-1000 <= nums[i] <= 1000
-10^7 <= k <= 10^7

分析:

拆解关键词:

【整数数组、正整数、负整数、整数K、连续子数组、求和】

想法:

🤔做了很多这种子数组的题目,相信大家和我一样,看了这种题,可能立马会浮现三种解决方式,那我们来一一分析是否可以继续使用如下三种方式:

1、暴力破解法

2、前缀和

3、滑动窗口法

解释:
【暴力破解法】

假设给定一个数组**【2,3,1,2】**,该数组存在的全部连续子数组的情况有如下:数组长度最小是1,最大是本身的长度。

⚠️分数组长度:

数组长度为1: 【2】【3】【1】【2】

数组长度为2: 【2,3】【3,1】【1,2】

数组长度为3:【2,3,1】【3,1,2】

数组长度为4:【2,3,1,2】

上面这种很好理解吧,就是分大小然后单独列出来,我这里将上面的数据换种方式展示【注意是一模一样的数据,只是换了分类方式】

⚠️分数组起始位置:index是下标位置 这里index的范围是0到3

index=0: 【2】【2,3】【2,3,1】【2,3,1,2】
index=1: 【3】【3,1】【3,1,2】
iddex=2: 【1】【1,2】
index=3: 【2】
可以发现,从起始位置开始向后扩范围,可以将所有的可能解挨个循环,这样可以保证不漏掉任何一个连续子数组。

所以我在描述暴力破解的时候,只需要每次从一个下标循环,然后在这个下标位置下二次循环,向后每次扩大一位数组,是可以得出全部的解。

【前缀和】

之前涉及到的前缀和是和二分查找一起使用的,这也就给了一个错觉,好像只有前缀和是递增或者递减的情况下才可以使用,但是错了,我错了,我不应该想的这么片面。这道题其实也可以使用前缀和。这里我会详细的描述前缀和的解法。

这里假设题目给了一个较为复杂且正负交叉存在的数组:【0,1,2,-2,1,-1,3,-3,6,10,-16】很复杂对吧,下面我们列举前缀和:

i012345678910
nums[i]012-21-13-3610-16
sums[i]-前缀和013121417171

上述数组sums[i]就从下标0为起始点的nums[0->i]的和。⚠️假设题目给的k=0,那么按照sums的结果来看,只有sums[0]这一个结果满足k,但是其实从肉眼来看,数组中有很多连续的和满足0的元素,比如【2,-2】【3,-3】等等,这些都是结果,但是从sums数组上却看不出来

🤔原因是什么呢?

原因就是sums是恒从下标0开始的累加值,所以看不到其他的存在的解。

那怎么看到其他潜在的解呢,有一个思路,假设要寻找【2,-2】,那么其实从我们个人解题来讲,需要找的是sums[3]-sums[1]的结果就是nums[2]+nums[3],加起来正好是符合和=0.

⚠️下面这个结论很重要,大家一定要理解,理解了才可以写出最终的代码:

💡得:如果满足`sums[i]`-`sums[j]`=`k`,且`i>j`那么存在新的解,新的解就是`下标i`-->`j`的和。反过来说,如果`sums[i]`-`k`的结果在`sums`数组中存在,那么也存在新的解。这两句话意思一样,一个是正着说,一个反着说,如果不理解可以多看几遍😝

整体思路已经明了,接下来就是扣细节的任务了:

  • 如果sums[i]-k的结果在sums数组中存在多个,比如我们题目所给的例子来看,sums[5]-sums[3]sums[5]-sums[1]的结果是一样的,那么反推过来sums[5]-k 肯定也是有两个解的【这里我们依旧假设k是题目要求为0】,这两个结果我们都得要。因为这就是代表多个起点不一致的连续满足解的子数组,当然都要了哈。

image.png

  • 那这里如何体现有几个解呢,可以使用map结构,来存储,以sums[i]-k的值为key,以sums[i]-k出现的个数为value,这样就可以做到体现有几个解了,而map结构中的key其实就是当前出现过的sums[j]的值(j<i)
【滑动窗口法】

考虑下这里是否可以使用滑动窗口,滑动窗口有左右指针构成,当不满足条件的时候,right指针右移,当满足满足条件且right继续右移已经不可能出现答案的时候那么右移left指针。企图通过移动left指针去发现新的解。

这里什么时候可以满足移动right指针不可能再出现答案呢? 不存在这个时候。假设当前是寻找sum=k,因为数据中存在正数和负数,所以窗口的right指针永远都需要继续移动,因为移动后sum可能变大也可能变大,可能寻找到新的解,直接遍历到元素末尾才能确定这一轮解最后的结果。

这种目标无法明确,窗口指针左右边界几乎是整个数组范围,这种情况下确实是无法使用滑动窗口,这个机制下使用滑动窗口无异于暴力破解。故放弃该解法。

代码:

第一版:暴力破解

image.png

class Solution {
    public int subarraySum(int[] nums, int k) {

        return first01(nums,k);
        
    }

    public static int first01(int[] nums,int k){

        int len = nums.length;
        int res_cnt = 0;

        for(int i=0;i<len;i++){

            int sum = nums[i];

            if(sum==k) res_cnt++;//判断是否存在一个元素直接等于k

            for(int j=i+1;j<len;j++){

                sum+=nums[j];//累加
                
                if(sum==k) res_cnt++; //如果累加结果等于k,那么更新结果解的个数
            }
        }
        return res_cnt;
    }
}
第二版:前缀和

image.png

class Solution {
    public int subarraySum(int[] nums, int k) {

        return first02(nums,k);
        
    }

    public static int first02(int[] nums,int k){

        int len = nums.length;
        int sum = 0;
        int res_cnt = 0;
        HashMap<Integer,Integer> map = new HashMap<>();

        for(int i=0;i<len;i++){

            sum = sum + nums[i]; //计算前缀和

            int tmp = sum - k; //获取tmp值,如果当前存在满足tmp的值,那么说明sums[i]至少一个存在一个下标j,使得sums[j]--》sums[i]满足连续子数组和=k

            if(tmp==0)res_cnt++;//如果tmp等于0,那么说明sums[i]本身是一个解,不需要去寻找sums[j].可以直接将结果更新
            
            res_cnt = res_cnt + map.getOrDefault(tmp,0); //加上tmp存在的个数 如果不存在那么+0

            map.put(sum,map.getOrDefault(sum,0)+1);//更新新的sum值,便于后续继续查找使用
        }
        return res_cnt;
    }
}

总结:

这道题给我最大的帮助,就是让我对前缀法的应用又多了一层了解,前缀法虽然计算每个元素从开始到截止位置前缀很简单,但是计算好之后我们如何使用这个和去实现题目要求才是最重要的,所以算法确实变化太多了,需要我们不断的去提高,去适应,去解决。💪🏻💪🏻💪🏻

大家好,我是二十一画,感谢您的品读,如有帮助,不胜荣幸~😊

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值