0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

LeetCode 560:和为K的子数组

算法与数据结构 来源:吴师兄学算法 作者:吴师兄学算法 2022-08-02 14:17 次阅读
大家好,我是吴师兄,

今天的题目来源于 LeetCode 第 560 号问题:和为 K 的子数组,难度为「中等」。

一、题目描述

给你一个整数数组nums和一个整数k,请你统计并返回该数组中和为k的子数组的个数

示例 1:

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

示例 2:

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

提示:

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

二、题目解析

补充知识点前缀和:前缀和指一个数组的某下标之前的所有数组元素的和(包含其自身)。

利用前缀和这种特点,可以快速的计算某个区间内的和,比如前 i 个元素的前缀和为preSum[i] = num[0] + nums[1] + ... + nums[i],而前 j 个元素的前缀和为preSum[j] = num[0] + nums[1] + ... + nums[j]

那么区间[ i , j ]之间的子数组之和就是 **preSum[j] - preSum[i]**。

81fa901c-1217-11ed-ba43-dac502259ad0.png

基于这种思路,可以先遍历一次数组,求出前缀和数组。

82155be0-1217-11ed-ba43-dac502259ad0.png

题目这个时候就变成了需要寻找出多少个 i 和 j 的组合,使得 [ i , j ] 这个区间的和为 k

classSolution{
publicintsubarraySum(int[]nums,intk){

intlen=nums.length;

int[]preSum=newint[len+1];

preSum[0]=0;

for(inti=0;i< len; i++) {
            preSum[i + 1]=preSum[i]+nums[i];
}

intcount=0;

for(inti=0;i< len; i++) {
          
            for(intj=i;j< len; j++) {
              
                if(preSum[j+1]-preSum[i]==k){
count++;
}
}
}
returncount;
}
}

在计算过程中,有两个 for 循环发生了嵌套,时间复杂度来到了 O(n^2) 级别。

需要优化

事实上,我们不需要去计算出具体是哪两项的前缀和之差等于k,只需要知道等于 k 的前缀和之差出现的次数 count,所以我们可以在遍历数组过程中,先去计算以 nums[i] 结尾的前缀和 pre,然后再去判断之前有没有存储 pre - k 这种前缀和,如果有,那么 pre - k 到 pre 这中间的元素和就是 k 了。

具体操作如下:

1、利用哈希表,以前缀和为键,出现次数为对应的值,记录 pre[i] 出现的次数。

2、开始从头到尾遍历 nums 数组,在遍历过程中,会执行两个操作。

3、存储索引为 i 的这个元素时,前缀和的值是多少,并且把这个值出现的频次存储到 mp 中。

823da2e4-1217-11ed-ba43-dac502259ad0.png

4、判断之前有没有存储 pre - k 这种前缀和,如果有,说明 pre - k 到 pre 直接的那些元素值之和就是 k。

5、返回结果。

三、参考代码

1、Java 代码

//登录AlgoMooc官网获取更多算法图解
//https://www.algomooc.com
//作者:程序员吴师兄
//代码有看不懂的地方一定要私聊咨询吴师兄呀
//和为 K 的子数组(LeetCode 560):https://leetcode.cn/problems/subarray-sum-equals-k/
classSolution{
publicintsubarraySum(int[]nums,intk){

//统计和为K的子数组的数量
intcount=0;

//记录遍历到索引为i的这个元素时,前缀和的值是多少
intpre=0;

//利用哈希表,以前缀和为键,出现次数为对应的值,记录pre[i]出现的次数
HashMapmp=newHashMap<>();

//一开始,需要设置前缀和为0时,出现的次数为1次
//这一行的作用就是为了应对nums[0]+nums[1]+...+nums[i]==k这种情况
//如数组[1,2,3,6]
//这个数组的累加和数组为[1,3,【6】,12]
//如果k=6,假如mp中没有预先存储(0,1)
//那么来到累加和为6的位置时,这时mp中存储的就只有两个数据(1,1),(3,1)
//想去判断mp.containsKey(pre-k),这时pre-k=6-6=0
//但map中没有(0,1),
//因为这个时候忽略了从下标0累加到下标i等于k的情况
//仅仅是统计了从下标大于0到某个位置等于k的所有答案
mp.put(0,1);

//开始从头到尾遍历nums数组,在遍历过程中,会执行两个操作
//1、存储索引为i的这个元素时,前缀和的值是多少,并且把这个值出现的频次存储到mp中
//2、判断之前有没有存储pre-k这种前缀和,如果有,说明pre-k到pre直接的那些元素值之和就是k
for(inti=0;i< nums.length; i++) {

            //存储索引为i的这个元素时,前缀和的值是多少
pre+=nums[i];

//判断之前有没有存储pre-k这种前缀和
if(mp.containsKey(pre-k)){

//如果有,说明pre-k到pre直接的那些元素值之和就是k
//找到了一组,累加到count上
count+=mp.get(pre-k);

}

//这个值出现的频次存储到mp中
// getOrDefault:当 Map 集合中有这个 key 时,就使用这个 key 对应的 value 值
//如果没有就使用默认值defaultValue
mp.put(pre,mp.getOrDefault(pre,0)+1);
}

//返回结果
returncount;
}
}

2、C++ 代码

classSolution{
public:
intsubarraySum(vector<int>&nums,intk){

//统计和为K的子数组的数量
intcount=0;

//记录遍历到索引为i的这个元素时,前缀和的值是多少
intpre=0;

//利用哈希表,以前缀和为键,出现次数为对应的值,记录pre[i]出现的次数
unordered_map<int,int>mp;

//一开始,需要设置前缀和为0时,出现的次数为1次
//这一行的作用就是为了应对nums[0]+nums[1]+...+nums[i]==k这种情况
//如数组[1,2,3,6]
//这个数组的累加和数组为[1,3,【6】,12]
//如果k=6,假如mp中没有预先存储(0,1)
//那么来到累加和为6的位置时,这时mp中存储的就只有两个数据(1,1),(3,1)
//想去判断mp.containsKey(pre-k),这时pre-k=6-6=0
//但map中没有(0,1),
//因为这个时候忽略了从下标0累加到下标i等于k的情况
//仅仅是统计了从下标大于0到某个位置等于k的所有答案
mp[0]=1;

//开始从头到尾遍历nums数组,在遍历过程中,会执行两个操作
//1、存储索引为i的这个元素时,前缀和的值是多少,并且把这个值出现的频次存储到mp中
//2、判断之前有没有存储pre-k这种前缀和,如果有,说明pre-k到pre直接的那些元素值之和就是k
for(inti=0;i< nums.size(); i++) {

            //存储索引为i的这个元素时,前缀和的值是多少
pre+=nums[i];

//判断之前有没有存储pre-k这种前缀和
if(mp.find(pre-k)!=mp.end()){

//如果有,说明pre-k到pre直接的那些元素值之和就是k
//找到了一组,累加到count上
count+=mp[pre-k];

}

//这个值出现的频次存储到mp中
mp[pre]++;
}

//返回结果
returncount;

}
};

3、Python 代码

classSolution:
defsubarraySum(self,nums:List[int],k:int)->int:
#统计和为K的子数组的数量
count=0

#记录遍历到索引为i的这个元素时,前缀和的值是多少
pre=0

#利用哈希表,以前缀和为键,出现次数为对应的值,记录pre[i]出现的次数
mp=collections.defaultdict(int)

#一开始,需要设置前缀和为0时,出现的次数为1次
#这一行的作用就是为了应对nums[0]+nums[1]+...+nums[i]==k这种情况
#如数组[1,2,3,6]
#这个数组的累加和数组为[1,3,【6】,12]
#如果k=6,假如mp中没有预先存储(0,1)
#那么来到累加和为6的位置时,这时mp中存储的就只有两个数据(1,1),(3,1)
#想去判断mp.containsKey(pre-k),这时pre-k=6-6=0
#但map中没有(0,1),
#因为这个时候忽略了从下标0累加到下标i等于k的情况
#仅仅是统计了从下标大于0到某个位置等于k的所有答案
mp[0]=1

#开始从头到尾遍历nums数组,在遍历过程中,会执行两个操作
#1、存储索引为i的这个元素时,前缀和的值是多少,并且把这个值出现的频次存储到mp中
#2、判断之前有没有存储pre-k这种前缀和,如果有,说明pre-k到pre直接的那些元素值之和就是k
foriinrange(len(nums)):

#存储索引为i的这个元素时,前缀和的值是多少
pre+=nums[i]

#判断之前有没有存储pre-k这种前缀和
#如果有,说明pre-k到pre直接的那些元素值之和就是k
#找到了一组,累加到count上
#利用defaultdict的特性,当presum-k不存在时,返回的是0
count+=mp[pre-k]

#这个值出现的频次存储到mp中
# getOrDefault:当 Map 集合中有这个 key 时,就使用这个 key 对应的 value 值
#如果没有就使用默认值defaultValue
mp[pre]+=1

#返回结果
returncount

四、复杂度分析

时间复杂度:O(n),其中 n 为数组的长度。我们遍历数组的时间复杂度为 O(n),中间利用哈希表查询删除的复杂度均为 O(1),因此总时间复杂度为 O(n)。

空间复杂度:O(n),其中 n 为数组的长度。哈希表在最坏情况下可能有 n 个不同的键值,因此需要 O(n) 的空间复杂度。

审核编辑 :李倩


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 数组
    +关注

    关注

    1

    文章

    417

    浏览量

    25965
  • leetcode
    +关注

    关注

    0

    文章

    20

    浏览量

    2334

原文标题:LeetCode 560:和为 K 的子数组

文章出处:【微信号:TheAlgorithm,微信公众号:算法与数据结构】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    FMC卡设计方案:202-基于TI DSP TMS320C6678、Xilinx K7 FPGA XC7K325T的高速数据处理核心板

    AD FMC卡 , FMC卡 , FMC卡模块 , XC7K420T处理板 , 图像FMC
    的头像 发表于 12-16 16:02 169次阅读
    FMC<b class='flag-5'>子</b>卡设计方案:202-基于TI DSP TMS320C6678、Xilinx <b class='flag-5'>K</b>7 FPGA XC7<b class='flag-5'>K</b>325T的高速数据处理核心板

    TPSM560R6HEVM使用手册

    电子发烧友网站提供《TPSM560R6HEVM使用手册.pdf》资料免费下载
    发表于 12-03 15:46 0次下载
    TPSM<b class='flag-5'>560</b>R6HEVM使用手册

    使用TPSM560R6EVM

    电子发烧友网站提供《使用TPSM560R6EVM.pdf》资料免费下载
    发表于 11-28 15:00 0次下载
    使用TPSM<b class='flag-5'>560</b>R6EVM

    指针数组和二维数组有没有区别

    指针数组和二维数组有没有区别?比如这样的两个代码。 int main(){ char *s1[] = { "hello", "world", "total" }; char s2[][6
    的头像 发表于 11-24 11:12 168次阅读

    C语言数组应用计算机导论A第6讲:数组

    C语言数组应用计算机导论A第6讲:数组
    发表于 11-20 15:33 0次下载

    DS560MB410EVM用户指南

    电子发烧友网站提供《DS560MB410EVM用户指南.pdf》资料免费下载
    发表于 11-15 15:27 0次下载
    DS<b class='flag-5'>560</b>MB410EVM用户指南

    labview字符串数组转化为数值数组

    在LabVIEW中,将字符串数组转换为数值数组是一项常见的任务,尤其是在处理数据采集、信号处理或用户输入时。 1. 理解LabVIEW的数据类型 在开始之前,了解LabVIEW中的数据类型是非
    的头像 发表于 09-04 17:47 2425次阅读

    使用stm32l451片,对ad7606进行3通道100k采样值跳动问题?

    本人使用的是stm32l451片,对ad7606进行3通道100k采样,采5000个点,硬件spi速度10m,主频80m,出来的数据fft计算之后的电流值不停地在跳动,请问是什么问题,采样部分用的是ll库函数,请大佬指点一下
    发表于 08-19 10:04

    面试常考+1:函数指针与指针函数、数组指针与指针数组

    在嵌入式开发领域,函数指针、指针函数、数组指针和指针数组是一些非常重要但又容易混淆的概念。理解它们的特性和应用场景,对于提升嵌入式程序的效率和质量至关重要。一、指针函数与函数指针指针函数:定义:指针
    的头像 发表于 08-10 08:11 899次阅读
    面试常考+1:函数指针与指针函数、<b class='flag-5'>数组</b>指针与指针<b class='flag-5'>数组</b>

    嵌入式中零长度数组基本操作方法

    就是一个长度0的数组,也就是说不包含任何元素的数组。零长度数组在C99标准中引入,并在C11中得到进一步的支持。其定义很简单,就是一个大小
    的头像 发表于 05-11 08:49 965次阅读
    嵌入式中零长度<b class='flag-5'>数组</b>基本操作方法

    深入探索KUKA KRL中的数组应用

    如果 CHAR 类型数组的所有数组元素都拥有相同的字符串,则不必单独初始化每个数组元素。忽略右侧的数组下标。(对于一维数组下标,不写下标。)
    的头像 发表于 04-18 10:37 1264次阅读
    深入探索KUKA KRL中的<b class='flag-5'>数组</b>应用

    数组和链表在内存中的区别 数组和链表的优缺点

    数组和链表在内存中的区别 数组和链表的优缺点  数组和链表是常见的数据结构,用于组织和存储数据。它们在内存中的存储方式以及优缺点方面存在一些显著的差异。本文将详细探讨这些差异以及它们的优缺点。 1.
    的头像 发表于 02-21 11:30 1056次阅读

    数组和链表有何区别

    数组和链表的区别,这个问题,不仅面试中经常遇到,考研的同学也得掌握才行。
    的头像 发表于 02-19 15:33 522次阅读
    <b class='flag-5'>数组</b>和链表有何区别

    PHP中数组的使用方法!

    PHP中数组的使用方法! PHP是一种广泛使用的网络编程语言,它的数组功能非常强大且灵活。数组是一种数据结构,它允许我们在单个变量中存储多个值。 在本篇文章中,我将详细解释PHP数组
    的头像 发表于 01-12 15:11 562次阅读

    IS 560伺服驱动器用户手册

    电子发烧友网站提供《IS 560伺服驱动器用户手册.pdf》资料免费下载
    发表于 01-05 11:01 1次下载