前缀和

实例:leet560
本题的关键在于如何快速得到某个子数组的和,比如说给你一个数组nums,让你实现一个接口sum(i, j),这个接口要返回nums[i..j]的和,而且会被多次调用,怎么实现这个接口?因为接口要被多次调用,显然不能每次都去遍历nums[i..j],有没有一种快速的方法在 O(1) 时间内算出nums[i..j]呢?这就需要前缀和技巧了。

前缀和的思路是这样的,对于一个给定的数组nums,我们额外开辟一个前缀和数组进行预处理:

int n = nums.length;
// 前缀和数组
int[] preSum = new int[n + 1];
preSum[0] = 0;
for (int i = 0; i < n; i++)
    preSum[i + 1] = preSum[i] + nums[i];

这个前缀和数组preSum的含义也很好理解,preSum[i]就是nums[0..i-1]的和。那么如果我们想求nums[i..j]的和,只需要一步操作preSum[j+1]-preSum[i]即可,而不需要重新去遍历数组了。

回到这个子数组问题,我们想求有多少个子数组的和为 k,借助前缀和技巧很容易写出一个解法:

class Solution {
    public int subarraySum(int[] nums, int k) {
        int n=nums.length;
        int[] presum=new int[n+1];
        presum[0]=0;
        for(int i=0;i<n;i++){
            presum[i+1]=nums[i]+presum[i];
        }

        int ansNum=0;

        for(int i=1;i<=n;i++){
            for(int j=0;j<i;j++){
                if(presum[i]-presum[j]==k){
                    ansNum++;
                }
            }
        }
        return ansNum;

    }
}

当然,这个解法还是可以继续优化的:
上面虽然使用了前缀和,但是后面用了双重遍历,即每遍历到一个值,都要与之前的 每个值 做相减的操作,这样时间复杂度非常高,对于这种“遍历数组来看 某个值 是否存在”的问题可以用 哈希表来优化。

对前缀和数组进行哈希表存值,然后对前缀和数组进行一个遍历,每到一个值,就查表看 presum[i] - target是否存在以及有几个。当然之前的遍历以生成前缀和数组的过程也可以整合进来,即整个过程变为:遍历原数组,每遇到一个新的值,就去生成前缀和的哈希表,同时看此时的sum - target 是否存在。

class Solution {
    public int subarraySum(int[] nums, int k) {
        int n=nums.length;
        HashMap<Integer,Integer> presum = new HashMap<>();
        presum.put(0,1);

        int ansNum=0;
        int sum_i=0;
        for(int i=0;i<n;i++){
            sum_i=sum_i+nums[i];
            int sum_j=sum_i-k;
            if(presum.containsKey(sum_j)){
                ansNum=ansNum+presum.get(sum_j);
            }
            presum.put(sum_i,presum.getOrDefault(sum_i,0)+1);
        }
        return ansNum;

    }
}

前缀和不难,却很有用,主要用于处理数组区间的问题

比如说,让你统计班上同学考试成绩在不同分数段的百分比,也可以利用前缀和技巧:

int[] scores; // 存储着所有同学的分数
// 试卷满分 150 分
int[] count = new int[150 + 1]
// 记录每个分数有几个同学
for (int score : scores)
    count[score]++
// 构造前缀和
for (int i = 1; i < count.length; i++)
    count[i] = count[i] + count[i-1];

这样,给你任何一个分数段,你都能通过前缀和相减快速计算出这个分数段的人数,百分比也就很容易计算了。

但是,稍微复杂一些的算法问题,不止考察简单的前缀和技巧。比如本文探讨的这道题目,就需要借助前缀和的思路做进一步的优化,借助哈希表记录额外的信息。可见对题目的理解和细节的分析能力对于算法的优化是至关重要的。

原文地址:https://www.cnblogs.com/shiji-note/p/14461574.html