ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

【随想录10-番外 】前缀和数组

2022-01-25 20:03:53  阅读:168  来源: 互联网

标签:10 arr 前缀 int 番外 随想录 数组 preSum sum


前缀和数组

学习自 小而美的算法技巧:前缀和数组

303. 区域和检索 - 数组不可变
304. 二维区域和检索 - 矩阵不可变
560. 和为 K 的子数组
1314. 矩阵区域和

区分是用前缀和还是用滑动窗口的关键是:元素是否有负数

一维前缀和

在这里插入图片描述
303. 区域和检索 - 数组不可变

为了将前缀和数组下标与原数组下标对应,
前缀和数组长度为 n+1
此时前缀和数组 i 位置元素代表 原数组中 前 i 个元素之和。

class NumArray {
    int []arr; 
    public NumArray(int[] nums) {
        arr = new int [nums.length+1];
        arr[0]=nums[0];
        for(int i =1 ; i<arr.length;i++){
            arr[i] = arr[i-1]+nums[i-1];
        }
    }
    
    public int sumRange(int left, int right) {
        return arr[right+1]-arr[left];
    }
}

二维前缀和

304. 二维区域和检索 - 矩阵不可变

在这里插入图片描述
二维矩阵前缀和

同样的,为了方便管理
我们在建立前缀和数组时,行数、列数均要比原数组多1
前缀和数组 arr [ 1 ] [ 1 ] 代表原来数组中 第一行到第一行 与第一列到第一列 的矩阵元素之和

比如

原数组:
3 1
5 6

它的前缀和数组为
0 0 0
0 3 3+1
0 3+5 3+1+5+6
arr [ 2 ] [ 2 ] 代表原来数组中 第一行到第二行 与第一列到第二列 的矩阵元素之和

这种条件下建立的前缀和数组的【i,j 】元素代表 原数组 中 从【0,0】 到【i-1,j-1】 所围成的矩形内的元素之和。

class NumMatrix {
    int [][] arr ;
    public NumMatrix(int[][] matrix) {
        int m = matrix.length, n = matrix[0].length;
      
        if (m == 0 || n == 0) return;
        arr = new int [m+1][n+1];
          // 第一行 第一列 无实际意义 从行列都是1 开始遍历
        for(int i=1;i<=m;i++){
            for(int j=1;j<=n;j++){
                arr[i][j]=arr[i-1][j]+arr[i][j-1]+matrix[i-1][j-1]-arr[i-1][j-1];
            }
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        return arr[row2+1][col2+1]-arr[row1][col2+1]-arr[row2+1][col1]+arr[row1][col1];
    }
}

在求解前缀和数组 arr【i,j】 个位置的前缀和时,需要利用到
arr【i-1,j】
arr【i,j-1】
arr【i-1,j-1】
原数组【i,j】
这四个位置的元素
在这里插入图片描述
图源leetcode-笨猪爆破组-题解

因为arr【i,j】 可以分为四部分,也就是上图的四种颜色不同的矩阵。
利用之前已经计算好的元素之和,来减少当前元素的计算量,要不然每次都遍历【i,j】内的元素再累加,时间负责度是很高的。

实现 【i,j】到 【m,n】 (m>= i , n >=j ) 围成的矩形内的元素之和

在这里插入图片描述
上图源自负雪明烛题解

//s(O,F) = arr[x1][y2+1] 
//s(O,E) = arr[x2+1][ y1]
//arr[x1][y1] 其实就是左上角的那一部分,因为多减去了一次这部分的面积,所以要加回来。 
arr[x2+1][y2+1]-arr[x1][y2+1]-arr[x2+1][y1]+arr[x1][y1]

前缀和思想

560. 和为 K 的子数组

在这里插入图片描述

暴力法
生成前缀和数组,然后每两个位置减一次,如果值为目标数字,res+1
能过,但是不优雅。

int subarraySum(int[] nums, int k) {
    int n = nums.length;
    // 构造前缀和
    int[] sum = new int[n + 1];
    sum[0] = 0; 
    for (int i = 0; i < n; i++)
        sum[i + 1] = sum[i] + nums[i];

    int ans = 0;
    // 穷举所有子数组
    for (int i = 1; i <= n; i++)
        for (int j = 0; j < i; j++)
            // sum of nums[j..i-1]
            if (sum[i] - sum[j] == k)
                ans++;

    return ans;
}

由于只关心次数,不关心具体的解,我们可以使用哈希表加速运算

看了很多人写的帖子来描述这个最优解,我最终找到了这个我能真正理解的版本

前缀和技巧:解决子数组问题

因为时间复杂度最大支出是这部分,可以考虑从这优化

for (int i = 1; i <= n; i++)
    for (int j = 0; j < i; j++)
        if (sum[i] - sum[j] == k)
            ans++;

第二层 for 循环在干嘛呢?翻译一下就是,在计算,有几个j能够使得sum[i]和sum[j]的差为 k。毎找到一个这样的j,就把结果加一。

sum[ i ] - sum [ j ] == k

交换一下 变量顺序

if (sum[j] == sum[i] - k)
    ans++;

如果可以实现直接记录下 有多少次 sum [ j ] 和sum[ i ]-k相等 ,那就相当于求出了结果。

因为不求具体是哪些元素,只求次数,所以可以用map在生成前缀和过程中记录下
每一个前缀和出现的次数将其放入map中,并实时更新

然后当下次更新前,判断是否有 sum - k 在map 中,如果有就加上该前缀和出现的次数。

代码:

class Solution {

    public int subarraySum(int[] nums, int k) {
        // key:前缀和,value:key 对应的前缀和的个数
        Map<Integer, Integer> preSumFreq = new HashMap<>();
        // 对于下标为 0 的元素,前缀和为 0,个数为 1
         //例如输入[1,1,0],k = 2 如果没有这行代码,则会返回0,漏掉了1+1=2,和1+1+0=2的情况
        //输入:[3,1,1,0] k = 2时则不会漏掉
        //因为presum[3] - presum[0]表示前面 3 位的和,所以需要map.put(0,1),垫下底
        //例子来源:作者:chefyuan
        //链接:https://leetcode-cn.com/problems/subarray-sum-equals-k/solution/de-liao-yi-wen-jiang-qian-zhui-he-an-pai-yhyf/

        preSumFreq.put(0, 1);

        int preSum = 0;
        int count = 0;
        for (int num : nums) {
            preSum += num;

            // 先获得前缀和为 preSum - k 的个数,加到计数变量里
            if (preSumFreq.containsKey(preSum - k)) {
             //   System.out.println("num= " + num +" preSum=  "+ preSum + " preSum-k= " + (preSum-k));
               // System.out.println("之前 count ="+count );
                count += preSumFreq.get(preSum - k);
                //System.out.println("之后 count ="+count );
            }

            // 然后维护 preSumFreq 的定义
            preSumFreq.put(preSum, preSumFreq.getOrDefault(preSum, 0) + 1);
        }
        return count;
    }
}

类似该算法思想的题:

其他前缀和题目:

1314. 矩阵区域和

标签:10,arr,前缀,int,番外,随想录,数组,preSum,sum
来源: https://blog.csdn.net/qq_41852212/article/details/122638383

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有