[LC] 308. Range Sum Query 2D - Mutable

本文介绍了两种解决二维矩阵中动态区间和问题的方法。一种是通过维护一个二维前缀和数组,实现更新和查询操作;另一种是使用二维二进制索引树(Binary Indexed Tree, BIT)进行高效更新和查询。这两种方法分别适用于不同场景,并提供了具体实现代码。

这一题是https://blog.youkuaiyun.com/chaochen1407/article/details/86572593 和 https://blog.youkuaiyun.com/chaochen1407/article/details/86610960的共同延伸。在2d - immutable里面,我们的做法的核心是记录每一个从(0, 0)开始到某点的区域面积,然后返回类似return sum[right][Top] - sum[left][Top] - sum[right][Bot] + sum[left][Bot];即可。而1d - mutable里面,我们binary index tree的做法是可以返回 0 ~ i的和 prefixSum(i),然后求range(i ,j) 就是prefixSum(j) - prefixSum(i - 1)。所以到了2d-mutable这里,我们把两者结合一下就好了。嗯,我们可以假想一下,如果binary index tree也可以2d化,然后我们可以 通过一个相对快速的方式拿到bitTreeSum(i, j) 对应2d-immutable里面的 sum[i][j]。那就可以了。这就是2d - binary index tree的基本思路。

但是!最开始,我还是想举出另一个相对简单的解法。

这个解法依旧是在update和建树的时候缓存某些东西cols,cols是一个二维数组,大小和这个2d array一样大,cols[i][j]存的东西就是matrix[i][0] ...  matrix[i][j]的和。所以每次update(row, col, val)的时候,都要进行一个o(n)的操作去更新cols[row][0]... cols[row][col]。这样的话,当求和的时候sumRegion(row1, col1, row2, col2)的时候,所要做的就很简单了,就是求(col[row1][col2] - col[row1][col1 - 1]) + (col[row1+ 1][col2] - col[row1 + 1][col1 - 1]) + .. (col[row2][col2] - col[row2][col1 - 1)即可,这个过程可以认为是O(m)。

根据上述描述,可以得到比较简单的代码如下:

    int[][] cols;
    public NumMatrix(int[][] matrix) {
        if (matrix.length == 0) return;

        this.cols = new int[matrix.length][matrix[0].length];
        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[i].length; j++) {
                this.cols[i][j] = matrix[i][j];
                if (j != 0) {
                    this.cols[i][j] += this.cols[i][j - 1];
                }
            }
        }
    }
    
    public void update(int row, int col, int val) {
        int originValue = this.cols[row][col];
        if (col > 0) originValue -= this.cols[row][col - 1];
        int delta = val - originValue;
        for (int i = col; i < this.cols[row].length; i++) {
            this.cols[row][i] += delta;
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        int result = 0;
        for (int i = row1; i <= row2; i++) {
            result += this.cols[i][col2];
            if (col1 > 0) {
                result -= this.cols[i][col1 - 1];
            }
        }
        
        return result;
    }

介绍完简单的就来难的。回到我们之前所说的2D binary index tree。整体操作基本就是1d binary index tree是二维for循环。在1d binary index tree的时候,bitArr[i]的子节点是bitArr[i + (i & -i)],父亲节点是bitArr[i - (i & -i)]。而对于一个2d binary index tree来说,bitArr[row][col]的父子节点的结构有点难理解,你可以认为遍历完一个bitArr[row][col]的所有子孙节点就是分两个维度在跑一维的binary index tree。所以就是一个二维for循环,外循环在跑row + (row & -row),内循环在跑col + (col & -col)。这样就能遍历完bitArr[row][col]的子孙节点了。求和和一维binary index tree也是一样的,可以直接求到的是以(0, 0)为起点到(row, col)的区域和,我们称之为getAreaSum。同样跑的是一个二维for循环,外层是row - (row & -row), 里层是 col - (col & -col),把所有遍历过的节点全加起来即可。而这里的getAreaSum(row, col),其实就等同于2d-immutable里面解法的sum[row][col],只是复杂度是O(logmn),而不是o(1)。所以sumRegion(row1, col1, row2, col2)其实就是getAreaSum(row2, col2) - getAreaSum(row1 - 1, col2) - getAreaSum(row2, col1 - 1) + getAreaSum(row1 -1, col1 - 1)。和1维的binary index tree的建树是一样的,就相当于对每个节点call一个update,这个update的delta值就是matrix里面的值。我尝试过像1维的binary index tree那样把建树的复杂度优化成O(mn)但是我失败了,所以建树的算法复杂度目前就是O(mn * logmn)。

根据上述描述,可以得到代码是这样的

    int[][] bitArr;
    int[][] origin;

    public NumMatrix(int[][] matrix) {
        if (matrix.length == 0 || matrix[0].length == 0) return;
        this.origin = new int[matrix.length][matrix[0].length];
        this.bitArr = new int[matrix.length + 1][matrix[0].length + 1];

        for (int i = 0; i < matrix.length; i++) {
            for (int j = 0; j < matrix[0].length; j++) {
                this.update(i, j, matrix[i][j]);
            }
        }
    }
    
    public void update(int row, int col, int val) {
        int delta = val - this.origin[row][col];
        this.origin[row][col] = val;
        for (int i = row + 1; i < this.bitArr.length; i = i + (i & -i)) {
            for (int j = col + 1; j < this.bitArr[0].length; j = j + (j & -j)) {
                this.bitArr[i][j] += delta;
            }
        }
    }
    
    public int sumRegion(int row1, int col1, int row2, int col2) {
        return this.getAreaSum(row2, col2) - this.getAreaSum(row1 - 1, col2)
            - this.getAreaSum(row2, col1 - 1) + this.getAreaSum(row1 - 1, col1 - 1);
    }
    
    public int getAreaSum(int row, int col) {
        int result = 0;
        for (int i = row + 1; i > 0; i = i - (i & -i)) {
            for (int j = col + 1; j > 0; j = j - (j & -j)) {
                result += this.bitArr[i][j];
            }
        }
        
        return result;
    }

 

[-agentlib:jdwp=transport=dt_socket,address=127.0.0.1:56882,suspend=y,server=n, -Drebel.base=C:\Users\Administrator\.jrebel, -Drebel.env.ide.plugin.build=2d7357bde6d770c5760a889e2d4bf4d26276ce12, -Drebel.env.ide.plugin.version=2025.2.0, -Drebel.env.ide.version=2024.3.1.1, -Drebel.env.ide.product=IU, -Drebel.env.ide=intellij, -Drebel.notification.url=http://localhost:54734, -Xshare:off, -agentpath:C:\Users\Administrator\AppData\Roaming\JetBrains\IntelliJIdea2024.3\plugins\jr-ide-idea\jrebel6\lib\jrebel64.dll, -agentpath:C:\Users\Administrator\AppData\Local\Temp\idea_libasyncProfiler_dll_temp_folder6\libasyncProfiler.dll=version,jfr,event=wall,interval=10ms,cstack=no,file=C:\Users\Administrator\IdeaSnapshots\Application_2025_11_04_141856.jfr,dbghelppath=C:\Users\Administrator\AppData\Local\Temp\idea_dbghelp_dll_temp_folder7\dbghelp.dll,log=C:\Users\Administrator\AppData\Local\Temp\Application_2025_11_04_141856.jfr.log.txt,logLevel=DEBUG, -XX:TieredStopAtLevel=1, -Dspring.output.ansi.enabled=always, -Dcom.sun.management.jmxremote, -Dspring.jmx.enabled=true, -Dspring.liveBeansView.mbeanDomain, -Dspring.application.admin.enabled=true, -Dmanagement.endpoints.jmx.exposure.include=*, -javaagent:C:\Users\Administrator\AppData\Local\JetBrains\IntelliJIdea2024.3\captureAgent\debugger-agent.jar, -Dkotlinx.coroutines.debug.enable.creation.stack.trace=false, -Ddebugger.agent.enable.coroutines=true, -Dkotlinx.coroutines.debug.enable.flows.stack.trace=true, -Dkotlinx.coroutines.debug.enable.mutable.state.flows.stack.trace=true, -Dfile.encoding=UTF-8, -Dsun.stdout.encoding=UTF-8, -Dsun.stderr.encoding=UTF-8]
最新发布
11-05
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值