从存钱罐到子数组:一个关于累加和的精妙问题|LeetCode 560 和为K的子数组
LeetCode 560 和为K的子数组
点此看全部题解 LeetCode必刷100题:一份来自面试官的算法地图(题解持续更新中)
生活中的算法
你有没有这样的经历:每天往存钱罐里存一些零钱,某一天突然想知道,从哪天开始存到哪天结束,刚好能凑够买一件心仪物品的钱?这其实就是在寻找"和为特定值的连续数字序列"。
这个问题在金融分析、数据处理等领域都有着广泛的应用。比如分析股票的累计收益,寻找特定增长区间;或是在气象数据中寻找累计降雨量达到特定值的时间段。
问题描述
LeetCode第560题"和为K的子数组"是这样描述的:给你一个整数数组 nums 和一个整数 k,请你统计并返回该数组中和为 k 的连续子数组的个数。
例如:
- 输入:nums = [1,1,1], k = 2
- 输出:2
- 解释:有两个子数组之和为 2:[1,1] 和 [1,1]
最直观的解法:暴力枚举法
最容易想到的方法是:枚举所有可能的子数组,计算它们的和,统计等于k的个数。
让我们用一个例子来模拟这个过程:
nums = [1,2,3], k = 3
检查所有子数组:
[1] -> sum = 1 ≠ 3
[1,2] -> sum = 3 = 3 ✓
[1,2,3] -> sum = 6 ≠ 3
[2] -> sum = 2 ≠ 3
[2,3] -> sum = 5 ≠ 3
[3] -> sum = 3 = 3 ✓
找到两个和为3的子数组
这种思路可以用Java代码这样实现:
public int subarraySum(int[] nums, int k) {
int count = 0;
// 枚举所有可能的起点
for (int start = 0; start < nums.length; start++) {
int sum = 0;
// 从起点开始累加
for (int end = start; end < nums.length; end++) {
sum += nums[end];
// 判断当前子数组之和是否等于k
if (sum == k) {
count++;
}
}
}
return count;
}
优化解法:前缀和 + 哈希表
仔细观察会发现,我们在计算子数组之和时做了很多重复计算。实际上,我们可以使用前缀和的概念来优化。更妙的是,我们可以结合哈希表来快速找到符合条件的子数组。
前缀和的原理
前缀和是到当前位置为止所有数的和。如果两个前缀和之差为k,那么这两个位置之间的子数组和就是k。
算法步骤(伪代码)
- 初始化哈希表,记录前缀和出现的次数
- 初始化前缀和为0,计数为1(空数组的前缀和)
- 遍历数组,对于每个位置:
- 更新前缀和
- 查找是否存在前缀和等于(当前前缀和-k)的记录
- 更新哈希表中前缀和的出现次数
示例运行
让我们用nums = [1,2,1,2], k = 3模拟这个过程:
初始状态:
前缀和Map = {0:1}
count = 0
1. 处理1:
前缀和 = 1
查找Map中是否有key为(1-3)=-2的记录:无
更新Map = {0:1, 1:1}
2. 处理2:
前缀和 = 3
查找Map中是否有key为(3-3)=0的记录:有,count += 1
更新Map = {0:1, 1:1, 3:1}
3. 处理1:
前缀和 = 4
查找Map中是否有key为(4-3)=1的记录:有,count += 1
更新Map = {0:1, 1:1, 3:1, 4:1}
4. 处理2:
前缀和 = 6
查找Map中是否有key为(6-3)=3的记录:有,count += 1
更新Map = {0:1, 1:1, 3:1, 4:1, 6:1}
Java代码实现
public int subarraySum(int[] nums, int k) {
// 存储前缀和及其出现次数
Map<Integer, Integer> prefixSumCount = new HashMap<>();
// 初始化前缀和为0的情况
prefixSumCount.put(0, 1);
int count = 0;
int prefixSum = 0;
for (int num : nums) {
// 更新前缀和
prefixSum += num;
// 查找是否存在前缀和等于(当前前缀和-k)的记录
count += prefixSumCount.getOrDefault(prefixSum - k, 0);
// 更新当前前缀和的出现次数
prefixSumCount.put(prefixSum,
prefixSumCount.getOrDefault(prefixSum, 0) + 1);
}
return count;
}
解法比较
让我们比较这两种解法:
暴力枚举法:
- 时间复杂度:O(n²)
- 空间复杂度:O(1)
- 优点:直观易懂
- 缺点:效率低,有大量重复计算
前缀和 + 哈希表法:
- 时间复杂度:O(n)
- 空间复杂度:O(n)
- 优点:一次遍历就能得到结果
- 缺点:需要额外空间存储前缀和信息
题目模式总结
这道题体现了几个重要的算法思想:
- 前缀和技巧:通过预处理简化区间和的计算
- 哈希表应用:用于快速查找满足条件的值
- 空间换时间:通过存储中间结果来提高效率
这种解题模式在很多问题中都有应用,比如:
- 和为k的最长子数组
- 连续数组(0和1数量相等)
- 矩阵区域和检索
解决此类问题的通用思路是:
- 考虑是否可以通过预处理简化重复计算
- 思考如何利用已计算的结果
- 关注数值之间的关系转换
- 考虑边界情况的处理
小结
通过这道题,我们不仅学会了如何找到和为k的子数组,更重要的是掌握了前缀和这个强大的预处理技巧,以及如何巧妙地结合哈希表来优化查找过程。
记住,当遇到需要频繁计算区间和的问题时,前缀和往往是一个很好的切入点。而当我们需要快速判断某个值是否存在时,哈希表常常能带来惊喜!
作者:忍者算法
公众号:忍者算法
从存钱罐到子数组:一个关于累加和的精妙问题|LeetCode 560 和为K的子数组的更多相关文章
- LeetCode——560. 和为K的子数组
给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数. 示例 1 : 输入:nums = [1,1,1], k = 2 输出: 2 , [1,1] 与 [1,1] 为两种不 ...
- Leetcode 560.和为k的子数组
和为k的子数组 给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数. 示例 1 : 输入:nums = [1,1,1], k = 2 输出: 2 , [1,1] 与 [1 ...
- LeetCode 560. 和为K的子数组(Subarray Sum Equals K)
题目描述 给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数. 示例 1 : 输入:nums = [1,1,1], k = 2 输出: 2 , [1,1] 与 [1,1] ...
- 力扣Leetcode 560. 和为K的子数组
和为K的子数组 给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数. 示例 : 输入:nums = [1,1,1], k = 2 输出: 2 , [1,1] 与 [1,1 ...
- 【LeetCode】560. 和为K的子数组
560. 和为K的子数组 知识点:数组:前缀和: 题目描述 给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数. 示例 输入:nums = [1,1,1], k = 2 ...
- 力扣 - 560. 和为K的子数组
目录 题目 思路1(前缀和) 代码 复杂度分析 思路2(前缀和+哈希表优化) 代码 复杂度分析 题目 560. 和为K的子数组 思路1(前缀和) 构建前缀和数组,可以快速计算任意区间的和 注意:计算区 ...
- 560. 和为K的子数组
Q: A: 1.暴力找所有可能的子数组,n^2个子数组,最长长度n,则n ^3. 2.n^2解法 从1~n-1各起点开始,一直找到结尾,n^2 class Solution { public: int ...
- 力扣题解-560. 和为K的子数组
题目描述 给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数. 示例 1 : 输入:nums = [1,1,1], k = 2 输出: 2 , [1,1] 与 [1,1] ...
- 【Leetcode】560. 和为K的子数组&974. 和可被 K 整除的子数组(前缀和+哈希表)
public class Solution { public int subarraySum(int[] nums, int k) { int count = 0, pre = 0; HashMap ...
- 【LeetCode】209. 长度最小的子数组
209. 长度最小的子数组 知识点:数组:前缀和:二分法:双指针:滑动窗口 题目描述 给定一个含有 n 个正整数的数组和一个正整数 target . 找出该数组中满足其和 ≥ target 的长度最小 ...
随机推荐
- Socket Tcp高密集信息广播转发强度测试
在有些场中存在着大量的消息广播转发,为了了解.net socket tcp在这方面的性能表现,所以做了一个比较极端信息广播转发强度测试.测试场景是以400个连接信息相互广播为测试用例就是当其中一个连接 ...
- 解决window.close()在谷歌浏览器不起作用
简单明了直接上解决方法: let url = ' '; // 空字符串中间要加空格 window.open(url, '_self').close();
- Django消息队列之django-rq
github:https://github.com/rq/django-rq RQ(Redis Queue),人如其名,用 redis 做的队列任务 redis ,众所周知, 它的列表可以做队列,rq ...
- Winform解决跨线程更新UI的问题
最近又拿起了Winform的程序,由于要起socket server,所以需要起线程,这里就遇到了经典的跨线程UI调用的问题. 如果什么都不写,直接由线程更新UI,会报错:线程间操作无效. 这里的解决 ...
- MySQL 8.0 为什么会放弃查询缓存?
什么是查询缓存? 查询缓存就是将一次查询结果存储在内存中,假如下一次查询结果在内存中,就直接在内存中读取. 设计初衷 当然是提高性能,通过缓存来减少解析器.优化器.存储引擎的执行时间. MySQL查询 ...
- 论文泛读《T-Miner: A Generative Approach to Defend Against Trojan Attacks on DNN-based Text Classification》
发表时间:2021 期刊会议:30th USENIX Security Symposium 论文单位:Virginia Tech 论文作者:Ahmadreza Azizi,Ibrahim Asadul ...
- 体验 DORIS 安装
1.概述 doris 是 百度提供一个MPP架构的分析性数据库. 下面介绍一下如何安装doris . 2.下载 我用的是centos 7.5 的虚拟器. https://doris.apache.or ...
- Epicor ERP成本稽核
很多制造企业存在成本差异过大,公司要求提高成本准确率,以便为产品成本分析提供数据支撑. A. 成本现状:成本差异分析,工时.费率.制造差异等出现各种不同情况,造成差异过大. B. 以下是Epicor的 ...
- 鸿蒙UI开发快速入门 —— part05:组件的样式复用
1. 为什么要样式复用? 如果每个组件的样式都需要单独设置,在开发过程中会出现大量代码在进行重复样式设置,虽然可以复制粘贴,但为了代码简洁性和后续方便维护,样式的复用就很有必要了. 为此,鸿蒙推出了可 ...
- 这些“人美话又多”的同事们:2022 Q1 招聘人员 评优名单公布
编辑 编辑 编辑 编辑 编辑 编辑 编辑 编辑 编辑 欢迎大家后台留言报名哈~