最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 什么是前缀和?

    正文概述 掘金(dyhtps)   2021-03-29   1119

    什么是前缀和

    前缀和指一个数组的某下标之前的所有数组元素的和(包含其自身)。前缀和分为一维前缀和,以及二维前缀和。前缀和是一种重要的预处理,能够降低算法的时间复杂度。

    什么是一维前缀和?

    一维前缀和的公式:sum[i] = sum[i-1] + arr[i] ; sum是前缀和数组, arr是内容数组。拥有前缀和数组后, 我们可以在O(1)的时间复杂度内求出区间和。

    [i, j]的区间和公式: interval [i, j] = sum[j] - sum[i - 1]

    和为K的子数组

    给定一个整数数组和一个整数 k,你需要找到该数组中和为 k 的连续的子数组的个数。

    示例 1 :
    
    输入:nums = [1,1,1], k = 2
    输出: 2 , [1,1] 与 [1,1] 为两种不同的情况。
    

    说明 :

    • 数组的长度为 [1, 20,000]。
    • 数组中元素的范围是 [-1000, 1000] ,且整数 k 的范围是 [-1e7, 1e7]。
    思路1: 使用前缀和

    首先遍历数组构建前缀和数组,在拥有前缀和数组后,通过双层循环计算数组中每一个子项与之前的子项之间的区间和(子数组的和)。此时的时间复杂度为O(n^2)。

    /**
     * @param {number[]} nums
     * @param {number} k
     * @return {number}
     */
    var subarraySum = function(nums, k) {
        const pre = []
        let count = 0
        
        // 构建前缀和数组
        for (let i = 0; i < nums.length; i++) {
            const a = nums[i]
            const b = pre[i - 1] === undefined ? 0 : pre[i - 1]
            pre[i] = a + b
        }
    
        // 使用前缀和,可以快速获得区间和
        for (let i = 0; i < nums.length; i++) {
            for (let j = 0; j <= i; j++) {
                // 计算区间和,查找到满足条件的区间和,count加一
                let intervalSum;
                if (j === 0) {
                    intervalSum = pre[i]
                } else if (j === i) {
                    intervalSum = nums[i]
                } else {
                    intervalSum = pre[i] - pre[j - 1]
                }
                if (intervalSum === k) {
                    count += 1
                }
            }
        }
    
        return count
    };
    
    思路1(优化): 使用前缀和 + 哈希

    如果只使用前缀和, 时间复杂度还是太高了。无法通过全部的测试用例,会超时。下面使用哈希表降低时间复杂度。

    我们之前知道区间和的公式等于k = sum[j] - sum[i - 1], 我们通过简单的移项可以得出这个公式sum[i - 1] = sum[j] - k。我们在遍历nums时,可以获得当前的前缀和,当前的前缀和减去k,可以得到我们需要找的另一个前缀和的大小,如果hash之中有记录,我们只需要获取hash中的记录,就可以知道有多少区间和等于k了。

    
    /**
     * @param {number[]} nums
     * @param {number} k
     * @return {number}
     */
     var subarraySum = function(nums, k) {
        let count = 0
        let preSum = 0
        let hash = {}
    
        for (let i = 0; i < nums.length; i++) {
            preSum += nums[i]
            const key = preSum - k
            if (hash[key]) {
               count += hash[key]
            }
            if (preSum === k) {
                count += 1
            }
    
            // 记录前缀和出现的次数
            if (!hash[preSum]) {
                hash[preSum] = 1
            } else {
                hash[preSum] += 1
            }    
        }
    
        return count
    };
    

    什么是二维前缀和?

    什么是二维前缀和?二维前缀和其实就是二维数组的前缀和,二维前缀和的计算过程如下:

    const arr = [
        [1, 1, 1],
        [1, 1, 1],
        [1, 1, 1],
    ]
    
    // 对于 i = 0 j = 0   preSum[0, 0] = arr[0, 0]
    // 对于 i = 0         preSum[0, j] = preSum[0, j-1] + arr[0, j]
    // 对于 j = 0         preSum[i, 0] = preSum[i-1, 0] + arr[i, 0]
    // 其他情况  j != 0  i != 0       preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] + src[i][j] - preSum[i - 1][j - 1];
    
    const preSum = [[], [], []]
    
    for (let i = 0; i < arr.length; i++) {
        for (let j = 0; j < arr[0].length; j++) {
            if (i == 0 && j == 0) {
                preSum[i][j] = arr[i][j]
            } else if (i == 0) {
                preSum[i][j] = preSum[i][j - 1] + arr[i][j]
            } else if (j == 0) {
                preSum[i][j] = preSum[i - 1][j] + arr[i][j]
            } else {
                preSum[i][j] = preSum[i - 1][j] + preSum[i][j - 1] + arr[i][j] - preSum[i - 1][j - 1]
            }
        }
    }
    // preSum
    [
        [1, 2, 3],
        [2, 4, 6],
        [3, 6, 9]
    ]
    

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

    给定一个二维矩阵,计算其子矩形范围内元素的总和,该子矩阵的左上角为 (row1, col1) ,右下角为 (row2, col2) 。

    什么是前缀和?

    上图子矩阵左上角 (row1, col1) = (2, 1) ,右下角(row2, col2) = (4, 3),该子矩形内元素的总和为 8。

    示例:

    给定 matrix = [
      [3, 0, 1, 4, 2],
      [5, 6, 3, 2, 1],
      [1, 2, 0, 1, 5],
      [4, 1, 0, 1, 7],
      [1, 0, 3, 0, 5]
    ]
    
    sumRegion(2, 1, 4, 3) -> 8
    sumRegion(1, 1, 2, 2) -> 11
    sumRegion(1, 2, 2, 4) -> 12
    
    
    思路

    首先求得二维数组对应的二维前缀和数组。接下来看下图:

    什么是前缀和?

    如果想求得A顶点到B定点的区域和,我们可以使用如下的办法

    sumRegion(A,B) = preSum(O,B) - preSum(O,C) - preSum(O,D) + preSum(O,E)

    递推公式:

    sumRegion(row2, col2, row1, col1) = preSum[row2][col2] − preSum[row2][col1−1] − preSum[row1−1][col2] + preSum[row1−1][col1−1]

    我们需要额外考虑当row2,col2,row1,col1等于0时的情况,我们额外在前缀和的一条边,一条列,这样可以避免大量的边界条件判断。

    什么是前缀和?

    此时的公式变为了:

    sumRegion(row2, col2, row1, col1) = preSum[row2 + 1][col2 + 1] − preSum[row2 + 1][col1] − preSum[row1][col2 + 1] + preSum[row1][col1]

    /**
     * @param {number[][]} matrix
     */
     var NumMatrix = function(matrix) {
        // 原矩阵
        this.matrix = matrix
        // 前缀和矩阵
        this.preSum = null
    
        if (matrix.length === 0 || matrix[0].length === 0) {
            return
        }
        
        // 构建前缀和矩阵
        this.preSum = []
        for (let i = 0; i < this.matrix.length; i++) {
            this.preSum.push([])
        }
        for (let i = 0; i < this.matrix.length; i++) {
            for (let j = 0; j < this.matrix[0].length; j++) {
                if (i == 0 && j == 0) {
                    this.preSum[i][j] = this.matrix[i][j]
                } else if (i == 0) {
                    this.preSum[i][j] = this.preSum[i][j - 1] + this.matrix[i][j]
                } else if (j == 0) {
                    this.preSum[i][j] = this.preSum[i - 1][j] + this.matrix[i][j]
                } else {
                    this.preSum[i][j] = this.preSum[i - 1][j] + this.preSum[i][j - 1] + this.matrix[i][j] - this.preSum[i - 1][j - 1]
                }
            }
        }
        // 前缀和矩阵额外添加一行一列
        // 添加一列
        for (let i = 0; i < this.preSum.length; i++) {
            this.preSum[i].unshift(0)
        }
        // 添加一行
        this.preSum.unshift(new Array(this.preSum[0].length).fill(0))
    };
    
    /** 
     * @param {number} row1 
     * @param {number} col1 
     * @param {number} row2 
     * @param {number} col2
     * @return {number}
     */
    NumMatrix.prototype.sumRegion = function(row1, col1, row2, col2) {
        if (this.preSum) {
            return this.preSum[row2 + 1][col2 + 1] + this.preSum[row1][col1] - this.preSum[row2 + 1][col1] - this.preSum[row1][col2 + 1]
        }
        return null
    };
    
    /**
     * Your NumMatrix object will be instantiated and called as such:
     * var obj = new NumMatrix(matrix)
     * var param_1 = obj.sumRegion(row1,col1,row2,col2)
     */
    

    起源地下载网 » 什么是前缀和?

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元