刷题笔记:力扣第560题-和为k的子数组

1.拿到本题后首先想到的是滑动窗口法,设置左右指针l和r,右指针一直前进,当前子数组和大于等于k时左指针也前进,和为k的时候结果计数值+1。完整代码如下:

cpp 复制代码
 1. int subarraySum(int* nums, int numsSize, int k) {
 2.     int l = 0, r = 0;
 3.     int cnt = 0;
 4.     int sum = 0;
 5.  
 6.     while (r < numsSize){
 7.         sum += nums[r++];
 8.         while (l < r && sum >= k){
 9.             if (sum == k) cnt++;
10.             sum -= nums[l++];
11.         }
12.     }
13.  
14.     return cnt;
15. }

2.需要注意两点:

(1)当子数组和大于k时,左指针只前进一步不一定能将和重新变成小于k,所以判断代码应该用while而不是if。

(2)内部while循环需要记得加l < r边界条件,防止越界,且一定不能有等于号,因为当执行完sum += numsr++后,r可能就已经越界了,但此时仍在while循环内部,此时若允许l <= r就会导致l越界。

3.滑动窗口方法在本题是不正确的,因为本题的数组中出现了负数!这样就不满足"右指针前进,子数组和一定变大;左指针前进,子数组和一定变小"的核心逻辑。本题正确的方法是使用"前缀和",即前缀和sumi的含义为数组从0到i所有元素的和。设和为k的子数组为i, i+1,... j,则可以得出:

sumj - sumi -- 1 = k

经过移项,可得:

sumi -- 1 = sumj -- k

所以只需要设置一个哈希表,将所有前缀和统计进去,每次寻找sumi -- 1并将它的次数加到结果中即可。

4.基于以上思想,可写出完整代码如下:

cpp 复制代码
 1. // uthash哈希节点,key保存前缀和,cnt保存该前缀和出现的次数
 2. typedef struct {
 3.     int key;
 4.     int cnt;
 5.     UT_hash_handle hh;
 6. } HashEntry;
 7.  
 8. // 子数组和为k的数量,前缀和+哈希表优化
 9. int subarraySum(int* nums, int numsSize, int k) {
10.     // 哈希表头初始化为空
11.     HashEntry* hashTable = NULL;
12.     HashEntry* entry = NULL;
13.     // 初始化前缀和0,出现次数为1,对应前缀和从0开始的基准
14.     entry = (HashEntry*)malloc(sizeof(HashEntry));
15.     entry->key = 0;
16.     entry->cnt = 1;
17.     HASH_ADD_INT(hashTable, key, entry);
18.     // res记录符合条件子数组总数
19.     int res = 0;
20.     // sum记录当前前缀和
21.     int sum = 0;
22.     // 遍历数组,计算前缀和
23.     for (int i = 0; i < numsSize; i++){
24.         sum += nums[i];
25.         // 需要查找的前缀和:sum - k
26.         int target = sum - k;
27.         HashEntry* tmp = NULL;
28.         // 在哈希表查找target前缀和
29.         HASH_FIND_INT(hashTable, &target, tmp);
30.         // 如果存在,累加它出现的次数到结果
31.         if (tmp) res += tmp->cnt;
32.         // 查找当前前缀和sum,准备更新哈希表
33.         HASH_FIND_INT(hashTable, &sum, tmp);
34.         if (tmp == NULL){
35.             // 不存在该前缀和,新建节点加入哈希表,次数初始化为1
36.             tmp = (HashEntry*)malloc(sizeof(HashEntry));
37.             tmp->key = sum;
38.             tmp->cnt = 1;
39.             HASH_ADD_INT(hashTable, key, tmp);
40.         } else {
41.             // 已存在,次数+1
42.             tmp->cnt++;
43.         }
44.     }
45.     return res;
46. }

该算法时间复杂度和空间复杂度均为O(n)。

5.需要注意,在一开始需要将"前缀和为0"直接放进哈希表中,出现次数为1。这样才能保证"当前前缀和正好等于k"时能正确地将结果+1。

6.本题核心代码中一定要保证"先查找前缀和,再添加当前前缀和"。因为当k = 0时,如果先添加了当前前缀和,就会在后续查到自己,导致结果错误地多加了1。在本题要做到"用现在的状态去匹配过去的历史记录",每次查找的都是历史记录,所以一定要坚守"先查后存"原则。

相关推荐
residual_fan1 小时前
航空发动机故障诊断专用智能体(四):深度强化学习与自适应奖励机制
人工智能·算法·数据挖掘·数据分析
2601_962218611 小时前
万象生鲜系统多终端统一数据协议PC手机PDA数据实时同步
大数据·数据库·人工智能·python·算法
AgentMaster1 小时前
企业元数据管理技术实战:从采集架构到血缘解析的完整方案
大数据·人工智能·算法
晴天的雨.9921 小时前
[C++算法]盛最多水的容器(双指针算法)
开发语言·c++·算法
摇滚侠1 小时前
《Spring Boot 3:高级与架构设计》第 1 章 元编程与元信息 个人理解 1
java·spring boot·笔记·后端
志尊宝1 小时前
Vue3 零基础每日笔记(046):声明式导航与编程式导航——useRouter / useRoute 双子星
vue.js·笔记·#vue #前端 #前端开发·#javascript·#vue.js
M78佐菲1 小时前
ARM学习笔记(四)
linux·arm开发·笔记·嵌入式硬件·学习
kkkkkkkkkk_Z2 小时前
单片机|学习日记:单片机底层架构与寄存器操作全解析
linux·笔记·单片机·学习·51单片机
不会就选b2 小时前
算法日常・每日刷题--<贪心>15
算法