给定一个二维矩阵,计算其子矩形范围内的元素之和,子矩阵左上角为(row1,col1)右下角是(row2,col2)。30142563211┏2━━0━━1┓54┃101┃71┗0━━3━━0┛5上图左上角的子矩阵(方框内)图(row1,col1)=(2,1),右下角(row2,col2)=(4,3),子矩形内元素之和为8。例子:给定矩阵=[[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)->8sumRegion(1,1,2,2)->11sumRegion(1,2,2,4)->12提示:你可以假设矩阵是不可变的。sumRegion方法将被调用多次。您可以假设row1≤row2和col1≤col2。以上就是题目给出的全部内容。我们可以先用最简单粗暴的方式写一个解题方法。最简单粗暴的方式当然是循环遍历。根据给定的子矩阵的左上角和右下角的坐标,循环遍历子矩阵中的所有点并相加,就是最终的结果。上面的代码:classNumMatrix:def__init__(self,matrix:list[list[int]]):self.matrix=matrixdefsumRegion(self,row1:int,col1:int,row2:int,col2:int)->int:sum_region=0forrowinrange(row1,row2+1):#loopcolumn(|)forcolinrange(col1,col2+1):#looprow(one)sum_region+=self.matrix[row][col]returnsum_region的方法时间复杂度和空间复杂度都是O(m*n),甚至通过了提交。本题结束,撒花。但是这道题不能就此打住,因为上面的解法虽然可以通过测试,但是使用的方法并不是这道题想要考察的方式。题目提示中提到sumRegion()方法会被多次调用。并且在给出的初始代码中,也暗示了在测试时会先实例化对象,然后再执行方法。根据上面的提示,我们可以知道这道题是为了让我们在实例化对象的时候对二维数组进行预处理,然后在调用sumRegion()方法的时候更高效的得到结果。那么,预热结束,正文开始:想一想,如果只有子矩阵的左上角和右下角的坐标,我如何才能尽快得到子矩阵所有点的和呢?子矩阵是给定的?我想到的第一个方法是计算矩阵中当前坐标和同一行中所有之前坐标的和,这样在计算子矩阵坐标和的时候,每一行坐标的和就可以了可以通过简单的计算得到,而不是使用遍历累加。如下图所示,新矩阵的每个坐标的值都是同一行之前坐标的和,所以在计算一行子区间和的时候,可以通过坐标右边界(图中蓝底红字)的值是左边界减去坐标再左移一位(图中橙底红字)的值。通过计算子矩阵的坐标值之和,可以遍历每一行的值并累加。该计算的时间复杂度降低为O(m)。代码如下:classNumMatrix:def__init__(self,matrix:list[list[int]]):self.matrix,self.colSum=matrix,[]foriinrange(0,len(self.matrix)):self.colSum.append([])col_sum=0forcolinself.matrix[i]:col_sum+=colself.colSum[i].append(col_sum)defsumRegion(self,row1:int,col1:int,row2:int,col2:int)->int:sum_region=0#只需要循环columns(|)forrowinrange(row1,row2+1):#每行右坐标的值减去左坐标然后左移一位坐标的值#因为左移后索引可能为-1,所以添加判断sum_region+=self.colSum[row][col2]-(self.colSum[row][col1-1]ifcol1!=0else0)returnsum_region的计算效率比原来的方法有了很大的提升,但是这样就够了吗?如果sumRegion()方法需要执行多次,那么总的时间复杂度就达到了O(m*n),原方法的时间复杂度是O(m*n*k)。那么,有没有更有效的方法呢?答案是肯定的,既然每一行的坐标都可以统计从第0行点的坐标到当前坐标的累加值,那么就可以提前计算出(0,0)坐标到当前坐标的子矩阵累加的坐标。子矩阵坐标值之和的计算只需要计算一次。假设左上角坐标为(A,B)和右下角坐标为(C,D)为子矩阵坐标值之和。现在(C,D)坐标的值已经是(0,0)到(C,D)的坐标值之和,那么这个值减去冗余位置的值就是要求的答案。然后子矩阵右上角坐标(A-1,D)减去a(0,0)子矩阵(黄色部分+红色部分),左下角坐标减去a(0,0)子矩阵的(C,B-1)子矩阵(紫色部分+红色部分)。这时发现两次减法运算中有重复的区域(红色部分),那么这部分需要再次相加,(0,0)到坐标(A,B)的子矩阵(红色部分)的子矩阵的左上角。计算公式为(坐标中的值为(0,0)到当前点的子矩阵坐标值之和,记为S):子矩阵坐标值之和=S(C,D)-S(A-1,D)-S(C,B-1)+S(A-1,B-1)计算子矩阵坐标值之和的问题是解决了,但是有个预先解决的问题,如何得到每个坐标相对于(0,0)坐标之间的子矩阵坐标值之和。无法对每个坐标点进行循环列加循环行的二维遍历,效率太低。其实可以理解为子矩阵坐标值之和的逆推。假设你想得到(A,B)坐标和(0,0)坐标之间的子矩阵坐标值之和,那么你可以使用(0,0)to(A-1,B)sub-矩阵(紫色部分+红色部分)和(0,0)到(A,B-1)子矩阵(黄色部分+红色部分),此时(A-1,B-1)子矩阵为计算两次,将这个子矩阵的值相减,最后加上(A,B)的坐标值,即(A,B)之间的子矩阵的坐标值之和B)坐标和(0,0)坐标。计算公式为(坐标中的值为(0,0)到当前点的子矩阵坐标值之和,记为S):子矩阵坐标值之和相对原点的坐标=(A,B)+S(A-1,B)+S(A,B-1)-S(A-1,B-1)此时初始化的时间复杂度并且计算原点的所有坐标和子矩阵坐标值之和为O(n*m),sumRegion()方法的时间复杂度为O(1),即使执行N次,总时间复杂度是O(N)。代码如下:classNumMatrix:def__init__(self,matrix:list[list[int]]):self.matrix,self.preSum=matrix,[]forrowinrange(0,len(self.matrix)):self.preSum.append([])forcolinrange(0,len(self.matrix[row])):#区域A:(0,0)->(A-1,B)区域B:(0,0)->(A,B-1)Repeatregion:(0,0)->(A-1,B-1)#索引值有减法运算,主索引边界区域A=self.preSum[row-1][col]ifrow>0else0areaB=self.preSum[row][col-1]ifcol>0else0repeatregion=self.preSum[row-1][col-1]if(row>0andcol>0)else0self.preSum[row].append(self.matrix[row][col]+regionA+regionB-重复区域)defsumRegion(self,row1:int,col1:int,row2:int,col2:int)->int:rangeA=self.preSum[row1-1][col2]ifrow1>0else0rangeB=self.preSum[row2][col1-1]ifcol1>0else0repeatrange=self.preSum[row1-1][col1-1]if(row1>0andcol1>0)else0returnself.preSum[row2][col2]-区域A-区域B+重复区域汇总,逐步优化,计算效率越来越高。我个人非常喜欢这种风格。先做最简单粗暴但效率低下的方法,然后一步步优化。最后放出这三种方式的耗时情况。公众号:《python杂货店》,专注于python语言及相关知识。发现更多原创文章,期待您的关注。关注并回复“python”获取python相关信息。
