算法总结-前缀和问题

算法总结-前缀和问题前缀和 preSum 是一种常用的 较为高校的预处理方式 相比于暴力解法 能够有效减低查询的时间复杂度

大家好,欢迎来到IT知识分享网。

前缀和(preSum)是一种常用的、较为高效的预处理方式,相比于暴力解法,能够有效减低查询的时间复杂度。

1、前缀和算法

前缀和其实就是数组的前n项之和,为了更好的理解和应用它,我们用一个题目来学习前缀和算法。

给你一个数组 nums 。数组「动态和」的计算公式为:runningSum[i] = sum(nums[0]…nums[i]) 。

请返回 nums 的动态和。

示例 1:

输入:nums = [1,2,3,4] 输出:[1,3,6,10] 解释:动态和计算过程为 [1, 1+2, 1+2+3, 1+2+3+4] 。

提示:

  • 1 <= nums.length <= 1000
  • -10^6 <= nums[i] <= 10^6

这是一个典型的前缀和题目。如果使用暴力解法,也可以做出这道题,但需要两个for循环,时间复杂度较大,暴力解法如下:

int* runningSum(int* nums, int numsSize, int* returnSize){ int *arr = malloc(sizeof(int) * (numsSize)); *returnSize = numsSize; for(int i = 0;i < numsSize;i++) { int temp = 0; for(int j = 0;j < i+1;j++) { temp += nums[j]; } arr[i] = temp; } return arr; }

如果用前缀和的解法,只需要一次循环,时间复杂度会大大降低。如下。

int* runningSum(int* nums, int numsSize, int* returnSize) { *returnSize = numsSize; for (int i = 1; i < numsSize; i++) { nums[i] += nums[i - 1]; } return nums; }

前缀和不仅代码简洁,性能上更是优于暴力解,因此能用前缀和来解决的题目应尽量避免使用暴力解法。

2、前缀和+哈希

前缀和有时会和哈希表一起使用,用来解决例如连续子数组之类的问题。如下问题

leetcode 560.和为k的子数组

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

提示:

这类问题很明显可以用前缀和来解答,但只使用前缀和在计算量较大的情况下效率仍然较低,可能会存在超时的问题,这时可以使用哈希表来进行优化。

前缀和问题的关键是找出所有前缀和为k的子数组,这个问题可以转换为”当前前缀和 – 前面前缀和 == k“,即preSum[i]  – preSum[j] = sum[i, j] ,也即preSum[i]  – preSum[j] = k。这样我们就可以用哈希表来存储每一个子数组和,以便于查找。

那么,如果存在preSum[i]  – preSum[j] = k,就可以利用哈希表记录preSum-k的元素,查找并计算preSum-k存在的次数即为目标值的个数。

代码如下:

struct hashtable { int key; int cnt; UT_hash_handle hh; }; struct hashtable *g_head = NULL; int subarraySum(int* nums, int numsSize, int k) { g_head = NULL; struct hashtable *node = (struct hashtable*)malloc(sizeof(struct hashtable)); node->key = 0; node->cnt = 1; HASH_ADD_INT(g_head, key, node); int sum_i = 0, ans = 0; for (int i = 0; i < numsSize; i++) { sum_i += nums[i]; int sum_j = sum_i-k; struct hashtable *s; HASH_FIND_INT(g_head, &sum_j, s); if (s != NULL) { ans += s->cnt; } HASH_FIND_INT(g_head, &sum_i, s); //查找当前preSum是否在哈希表,没有则将当前preSum入哈希表 if (s == NULL) { s = (struct hashtable*)malloc(sizeof(struct hashtable)); s->key = sum_i; s->cnt = 1; HASH_ADD_INT(g_head, key, s); } else { s->cnt++; } } return ans; } 

这个题目的关键,一是要彻底理解hash表中所存放值的含义;二是要理解返回值为什么是ans += s->cnt;而不是ans++。

我们以一个例子来理解这两个问题,假设数组为[1, 0, 3, 2, 4],所求的目标值为5,那么其前缀和如下(假设下标从1开始):

sum[1] = 1;

sum[2] = 1;

sum[3] = 4;

sum[4] = 6;

sum[5] = 10;

这几个值是要存到hash表中去的,其在hash表中的键值对为(key-value):1-2,4-1,6-1,10-1,我们到hash表中查找时,是按其key值检索的。

那么sum – k分别为:

[-4, -4, -1, 1,5]

这几个值就是我们要在哈希表中查找的,如果存在,那么就是我们的目标值,找到其对应的value即可,value就是其个数。比如以上例子我们的目标值为5,那么sum – k值为1的时候,哈希表中是有对应值的,即键值对1-2,这个就是我们要找的值,其结果为2,此时当前的前缀和为sum[4] = 6,所要减去的前缀和为sum[0]、sum[1](因为两个值都为1)。

对于第二个问题,用一个简单的例子就可以说明。一般情况下,hash表中的键值对都是x-1的形式,但有些特殊情况下例外,如数组为[1, -1, 0, 0],目标值为0的情况,这时

sum[0] = 1;

sum[1] = 0;

sum[2] = 0;

sum[3] = 0;

将各前缀和存到hash表中后,值为0的key,有3个,分别为sum[1]、sum[2]、sum[3],但在哈希表实际上占用一个键值对,即0-3。假如i = 3,那么当前前缀和为sum[3] = 0,以0 – k = 0到哈希表中查找目标值,此时会有两个值满足条件,即sum[1]、sum[2],其逻辑为,对于当前前缀和sum[3],存在前缀和sum[1]、sum[2]使得sum[3] – sum[1]或sum[2],其结果值为k。

这样的话,对于sum[3]而言,满足条件的值就不是一个了,所以计算结果时要用ans += s->cnt,而不是ans++来计算。

总结:要真正理解hash表中存的值是什么,hash表中存的是sum[0] – sum[i]的各个前缀和(不包括中间截取的情况)。而我们要拿去hash表查找的key,其实是sum-k,也就是当前前缀和之前的前缀和(如sum[3]之前的前缀和sum[1]或sum[2]),如果这个值存在hash表中,就说明条件成立。

解决这类问题要注意问题的转变,比如前面题目中我们将前缀和的问题转换为了hash表中寻找sum-k的问题,这时我们的问题已经转变,不再是求子数组的个数,而是求hash表中sum-k的个数,当问题转变时,条件也会转变,切不要将转变后的问题和条件与原来的问题和条件相混淆。

最后还应该释放哈希表。

 HASH_ITER(hh, g_head, current, next) { if (current) { HASH_DEL(g_head, current); } }

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://haidsoft.com/122765.html

(0)
上一篇 2025-10-14 11:00
下一篇 2025-10-14 11:15

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信