前缀和数组

217 阅读3分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第5天,点击查看活动详情

前言

今天做了力扣的一道题目,一位只是简单的双指针,但后续发现,有点问题,看过题解后,发现适合使用前缀和,奈何之前对前缀和没什么了解,这里就来简单的介绍一下

例题

数组不可变

链接:303. 区域和检索 - 数组不可变 - 力扣(LeetCode)

代码:

class NumArray {
    preSum:number[] = [0];
    constructor(nums: number[]) {
        //首先设置一个前缀和数组 
        let sum = 0
        for(let value of nums){
            sum+=value
            this.preSum.push(sum)
        }
    }
    
    sumRange(left: number, right: number): number {
        return this.preSum[right+1]-this.preSum[left]
    }
}

这是一个设计题,用到的思想就是前缀和思想

其实结合题目的意思,我们可以理解出,其实我们就是需要求出,left到right之间的sum

一般我们会怎么做呢?

sumRange(0, 2) = num[0]+num[1]+num[2]

sumRange(2, 5) = num[2]+ num[3]+ num[4]+ num[5]

但是如果我们转化一下,你看

sumRange(0, 2) = sumRange(0,2)-sumRange(0,0)

sumRange(2, 5) = sumRange(0,5)-sumRange(0,2)

这么一理解的话,假如我们知道sumRange(0,num[i]),那我们计算这道题,不是很简单嘛?

所以我们就用一个数组,记录所有的sumRange(0,num[i]):preSum:number[] = [0];

但是,你可能会疑惑,为什么第一个是0,而不是num[0]呢?

我的理解是:sumRange(0,0):前0个,这肯定是就是0

记住:preSum[i] 记录的其实就是num[0 ... i-1]的和

你可能又会有疑惑,那为啥是preSum[right+1]-preSum[left]

假如你要获取num[2]的前缀和,你回去找preSum里面的第几个?

答:第三个,为什么,因为:preSum[i] 记录的其实就是num[0 ... i-1]的和

矩阵不可变

链接:304. 二维区域和检索 - 矩阵不可变 - 力扣(LeetCode)

代码:

class NumMatrix {
    // 设置一个前缀和数组
    preSum:number[][] = []
    constructor(matrix: number[][]) {
        let m = matrix.length;
        let n = matrix[0].length;
        if(m === 0 || n=== 0){
            return;
        }
        for (let i = 0; i <= m; i++) {
            this.preSum[i]=[]
            for (let j = 0; j <= n; j++) {
                this.preSum[i][j]=0
            }
        }

        for(let i = 1;i<=m;i++){
            for(let j = 1;j<=n;j++){
                this.preSum[i][j] = this.preSum[i-1][j]+this.preSum[i][j-1]+matrix[i-1][j-1]-this.preSum[i-1][j-1]
            }
        }
    }

    sumRegion(row1: number, col1: number, row2: number, col2: number): number {
        return this.preSum[row2+1][col2+1]-this.preSum[row1][col2+1]-this.preSum[row2+1][col1]+this.preSum[row1][col1]
    }
}

可能你会有点懵,那么我们来理解一下,和之前的那个数组不可变类似

我们也是需要一个preSum,preSum[i,j]:记录的是region(0,0,i-1,j-1)的和

首先,我们先展示出数组

number数组.png

假设我们现在要求出range(2,1,4,3),那么我们确实可以一次次的去遍历找出

但是其实我们有一个更好的办法:

  • 我们要求的是蓝色区域,我们可以用整个那个区域range(0,0,4,3)
  • 减去橙色区域range(0,0,1,3)
  • 减去黄色区域range(0,0,4,0)
  • 加上绿色区域range(0,0,1,0):因为我们减了两次,所以需要补一次回来

那么对应我们的range(int row1, int col1, int row2, int col2)

我们将减去的其实就是

  • 减去橙色区域range(0,0,row1-1,col2)
  • 减去黄色区域range(0,0,row2,col1-1)
  • 加上绿色区域range(0,0,row1-1,col1-1)

现在我们来求一下前缀和

preSum数组.png

对着刚刚得出得出来的结论

  • 减去橙色区域前缀和presum(2,4)
  • 减去黄色区域前缀和preSum(5,1)
  • 加上绿色区域前缀和preSum(2,1)

那么对应我们的range(int row1, int col1, int row2, int col2)

我们将减去的其实就是

  • 减去橙色区域preSum(row1-1+1,col2+1)
  • 减去橙色区域preSum(row2+1,col1-1+1)
  • 加上绿色区域preSum(row1-1+1,col1-1+1)

那么这样你就能理解这道题目的意思

我觉得,这就像高中的那种割补法

总结

当然这两道题是不够的,后续再多做题吧,多总结吧

后续慢慢补充这个