
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。在本题要做到"用现在的状态去匹配过去的历史记录",每次查找的都是历史记录,所以一定要坚守"先查后存"原则。