6292. 子矩阵元素加 1

94 阅读3分钟

题目:
给你一个正整数 n ,表示最初有一个 n x n 、下标从 0 开始的整数矩阵 mat ,矩阵中填满了 0 。

另给你一个二维整数数组 query 。针对每个查询 query[i] = [row1i, col1i, row2i, col2i] ,请你执行下述操作:

  • 找出 左上角 为 (row1i, col1i) 且 右下角 为 (row2i, col2i) 的子矩阵,将子矩阵中的 每个元素 加 1 。也就是给所有满足 row1i <= x <= row2i 和 col1i <= y <= col2i 的 mat[x][y] 加 1 。

返回执行完所有操作后得到的矩阵 mat 。

算法:
方法一:模拟

func rangeAddQueries(n int, queries [][]int) [][]int {
    ans := make([][]int, n)
    for i := range ans {
        ans[i] = make([]int, n)
    }
    
    for i := range queries {
        for j := queries[i][0]; j <= queries[i][2]; j ++ {
            for k := queries[i][1]; k <= queries[i][3]; k ++ {
                ans[j][k] = ans[j][k] + 1
            }
        }
    }
    return ans
}

方法二:差分数组
方法一的暴力求解,如果querie次数很多,或者范围特别大,则可能时间复杂度达到O(n^3)。求对一维,二维区间多次运算后的结果,可以使用差分思想:

  • 一维差分
    差分数组diff的每个元素是diff[i] = nums[i] - nums[i - 1],如果对nums[i,j]进行区间+3,则只需要设置diff[i] = diff[i] + 3, diff[j + 1] = diff[j + 1] - 3。进行n这这样的运算,每次运行我们都可以在O(1)时间内完成。 然后我们利用 diff[i] = nums[i] - nums[i - 1]的特性,遍历遍历diff,num[i] = diff[i] + diff[i - 1],得到原始数组nums。

  • 二维差分
    一维前缀和prefix[i]是所有小于等于i的元素之和,类似的,二维前缀和prefixSum[i,j]的定义是:nums中所有row <= i, col <= j的元素之和(即nums[i,j]及其左上角的所有元素之和)。它可以用来求nums某个矩形范围内的和。
    如果nums图中的位置增加1,则黄色区域所有元素的前缀和都增加1

image.png
如果只对蓝色的区域增加1,差分数据会进行如下变化:
image.png

每次query操作我们对二维差分数组进行上述操作即可,如何根据差分数组的到前缀和数组呢?从row,col两个方向求和就行

func rangeAddQueries(n int, queries [][]int) [][]int {
    diff := make([][]int, n + 1)
    for i := range diff {
        diff[i] = make([]int, n + 1)
    }
    ans := make([][]int, n )
    // update二维差分数组
    for i := range queries {
        diff[queries[i][0]][queries[i][1]] ++
        diff[queries[i][0]][queries[i][3]  + 1] --
        diff[queries[i][2]  + 1][queries[i][1]] --
        diff[queries[i][2] + 1][queries[i][3] + 1] ++
    }
    // diff数组多出来的位置在d[n][n],第0行和第0列是有保存数据的
    // 有差分数组构造ans数组
    for i := 0; i < n; i ++ {
        for j := 1; j < n; j ++ {
            diff[i][j] = diff[i][j - 1] + diff[i][j]
        }
    }

    for i := 1; i < n; i ++ {
        for j := 0; j < n; j ++ {
            diff[i][j] = diff[i - 1][j] + diff[i][j]
        }
    }
    for i := 0; i < n; i ++ {
        ans[i ] = make([]int, n)
        for j := 0; j < n; j ++ {
            ans[i][j] = diff[i][j]
        }
    }
    return ans
}

另一种写法:

func rangeAddQueries(n int, queries [][]int) [][]int {
    diff := make([][]int, n + 1)
    ans := make([][]int, n + 1)
    for i := range diff {
        diff[i] = make([]int, n + 1)
        ans[i] = make([]int, n + 1)
    }

    for i := range queries {
        r1, c1, r2, c2 := queries[i][0],queries[i][1],queries[i][2],queries[i][3]
        diff[r1][c1] ++ 
        diff[r2 + 1][c1] --
        diff[r1][c2 + 1] --
        diff[r2 + 1][c2 + 1] ++ 
    }   
    // 还原
    
    for i := 1; i <= n; i ++ {
        for j := 1; j <= n; j ++ {
            ans[i][j] = ans[i - 1][j] + ans[i][j - 1] - ans[i - 1][j - 1] + diff[i - 1][j - 1]
        }
    }
    ans = ans[1:]
    for i := range ans {
        ans[i] = ans[i][1:]
    }
    
    return ans
}

关键在于理解上面的ans数组是prefixSum数组