赞
踩
前缀和(preSum)是一种常用的、较为高效的预处理方式,相比于暴力解法,能够有效减低查询的时间复杂度。
前缀和其实就是数组的前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;
- }
前缀和不仅代码简洁,性能上更是优于暴力解,因此能用前缀和来解决的题目应尽量避免使用暴力解法。
前缀和有时会和哈希表一起使用,用来解决例如连续子数组之类的问题。如下问题
leetcode 560.和为k的子数组
给你一个整数数组 nums 和一个整数 k ,请你统计并返回 该数组中和为 k 的子数组的个数 。
示例 1:输入:nums = [1,2,3], k = 3
输出:2
提示:
1 <= nums.length <= 2 * 104
-1000 <= nums[i] <= 1000
-107 <= k <= 107
这类问题很明显可以用前缀和来解答,但只使用前缀和在计算量较大的情况下效率仍然较低,可能会存在超时的问题,这时可以使用哈希表来进行优化。
前缀和问题的关键是找出所有前缀和为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);
- }
- }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。