• 前缀和


    实例: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];
    

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

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

  • 相关阅读:
    PAT:1002. 写出这个数 (20) AC
    PAT:1031. 查验身份证(15) AC
    PAT:1021. 个位数统计 (15) AC
    NSDate
    iOS程序的生命的周期
    Swift swith语句
    Swift 循环语句
    Swift 基本运算符
    Swift 解包
    Swift 可选类型(补充)
  • 原文地址:https://www.cnblogs.com/shiji-note/p/14461574.html
Copyright © 2020-2023  润新知