题目一:leetcode303.区域和检索 - 数组不可变
1.题目描述
给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。
示例:
给定 nums = [-2, 0, 3, -5, 2, -1],求和函数为 sumRange()
sumRange(0, 2) -> 1
sumRange(2, 5) -> -1
sumRange(0, 5) -> -3
说明:你可以假设数组不可变。
会多次调用 sumRange 方法。
在真实的面试中遇到过这道题?
2.解题思路
每次for循环求和会超时。
前缀和
我们用动态规划记录前i的和,那么sum(i, j) = dp[j+1] - dp[i]
为了方便计算加个0
时间复杂度:
初始化:O(n)
求和:O(1)
空间复杂度:O(n)
作者:powcai
链接:https://leetcode-cn.com/problems/range-sum-query-immutable/solution/qian-zhui-he-by-powcai-2/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
3.代码实现
class NumArray(object):
def __init__(self, nums):
"""
:type nums: List[int]
"""
self.dp = [0]
for num in nums:
self.dp.append(self.dp[-1]+num)
def sumRange(self, i, j):
"""
:type i: int
:type j: int
:rtype: int
"""
return self.dp[j+1] - self.dp[i]
# Your NumArray object will be instantiated and called as such:
# obj = NumArray(nums)
# param_1 = obj.sumRange(i,j)
题目二:leetcode307.区域和检索 - 数组可修改
1.题目描述
给定一个整数数组 nums,求出数组从索引 i 到 j (i ≤ j) 范围内元素的总和,包含 i, j 两点。
update(i, val) 函数可以通过将下标为 i 的数值更新为 val,从而对数列进行修改。
示例:
Given nums = [1, 3, 5]
sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8
说明:数组仅可以在 update 函数下进行修改。
你可以假设 update 函数与 sumRange 函数的调用次数是均匀分布的。
在真实的面试中遇到过这道题?
2.解题思路
同样是利用分块区域和的思路,下面这种方法使用了一种新的数据结构,叫做 树状数组Binary Indexed Tree,又称 Fenwick Tree,这是一种查询和修改复杂度均为 O(logn) 的数据结构。这个树状数组比较有意思,所有的奇数位置的数字和原数组对应位置的相同,偶数位置是原数组若干位置之和,假如原数组 A(a1, a2, a3, a4 ...),和其对应的树状数组 C(c1, c2, c3, c4 ...)有如下关系:
C1 = A1
C2 = A1 + A2
C3 = A3
C4 = A1 + A2 + A3 + A4
C5 = A5
C6 = A5 + A6
C7 = A7
C8 = A1 + A2 + A3 + A4 + A5 + A6 + A7 + A8
...
那么是如何确定某个位置到底是有几个数组成的呢,原来是根据坐标的最低位 Low Bit 来决定的,所谓的最低位,就是二进制数的最右边的一个1开始,加上后面的0(如果有的话)组成的数字,例如1到8的最低位如下面所示:
坐标 二进制 最低位
1 0001 1
2 0010 2
3 0011 1
4 0100 4
5 0101 1
6 0110 2
7 0111 1
8 1000 8
...
最低位的计算方法有两种,一种是 x&(x^(x–1))
,另一种是利用补码特性 x&-x。
这道题我们先根据给定输入数组建立一个树状数组 bit,比如,对于 nums = {1, 3, 5, 9, 11, 13, 15, 17},建立出的 bit 数组为:
bit -> 0 1 4 5 18 11 24 15 74
注意到我们给 bit 数组开头 padding 了一个0,这样我们在利用上面的树状数组的性质时就不用进行坐标转换了。可以发现bit数组中奇数位上的数字跟原数组是相同的,参见上面标记蓝色的数字。偶数位则是之前若干位之和,符合上图中的规律。
现在我们要更新某一位数字时,比如将数字5变成2,即 update(2, 2),那么现求出差值 diff = 2 - 5 = -3,然后我们需要更新树状数组 bit,根据最低位的值来更新后面含有这一位数字的地方,一般只需要更新部分偶数位置的值即可。由于我们在开头 padding了个0,所以我们的起始位置要加1,我们的j从位置i+1开始,即 j=3,然后现将 bit[3] 更新为2,然后更新下一位,根据图中所示,并不是 bit[3] 后的每一位都需要更新的,下一位需要更新的位置的计算方法为 j += (j&-j),这里我们的j是3,则 (j&-j) = 1,所以下一位需要更新的是 bit[4],更新为15,现在j是4,则 (j&-j) = 4,所以下一位需要更新的是 bit[8],更新为71,具体的变换过程如下所示:
0 1 4 5 18 11 24 15 74
0 1 4 2 18 11 24 15 74
0 1 4 2 15 11 24 15 74
0 1 4 2 15 11 24 15 71
接下来就是求区域和了,直接求有些困难,我们需要稍稍转换下思路。比如若我们能求出前i个数字之和,跟前j+1个数字之和,那么二者的差值就是要求的区间和了。所以我们先实现求前任意i个数字之和,当然还是要利用树状数组的性质,此时正好跟 update 函数反过来,我们的j从位置i开始,每次将 bit[j] 累加到 sum,然后更新j,通过 j -= (j&-j),这样就能快速的求出前i个数字之和了,从而也就能求出任意区间之和了
参考:https://www.cnblogs.com/grandyang/p/4985506.html
3.代码实现
class NumArray(object):
def __init__(self, nums):
"""
:type nums: List[int]
"""
n = len(nums)
self.data = [0 for _ in range(n)]
self.bit = [0 for _ in range(n+1)]
for i in range(n):
self.update(i, nums[i])
def update(self, i, val):
"""
:type i: int
:type val: int
:rtype: None
"""
diff = val - self.data[i]
# 这句的顺序很重要,不可以放在前面去
self.data[i] = val
j = i+1
while j < len(self.bit):
self.bit[j] = self.bit[j] + diff
j = j + (j&-j)
def getSum(self,i):
res = 0
j = i
while j > 0:
res = res + self.bit[j]
j = j - (j&-j)
return res
def sumRange(self, i, j):
"""
:type i: int
:type j: int
:rtype: int
"""
return self.getSum(j+1) - self.getSum(i)
# Your NumArray object will be instantiated and called as such:
# obj = NumArray(nums)
# obj.update(i,val)
# param_2 = obj.sumRange(i,j)
题目三:lc304.二维区域和检索 - 矩阵不可变
1.题目描述
给定一个二维矩阵,计算其子矩形范围内元素的总和,该子矩阵的左上角为 (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
说明:你可以假设矩阵不可变。
会多次调用 sumRegion 方法。
你可以假设 row1 ≤ row2 且 col1 ≤ col2。
2.解题思路
动态规划
首先要会求一个矩阵每个元素(i, j)的左上方的元素之和;
比如 :
3 7 1 3 10 11
2 4 0 --> 5 16 17
9 4 2 14 29 32
得到此矩阵dp,接下来要知道!
sumRegion(row1, col1, row2, col2) = dp[row2][col2] - dp[row2][col1 - 1] - dp[row1 - 1][col2] + dp[row1 - 1][col1 - 1]
为什么呢?我们画图说明,还是以上面例子求sumRegion(2, 2, 1, 1):
等于黄色的部分总和 - 两个橙色总和 + 红色部分 ( 因为我们发现当我们减去橙色部分, 红色部分多删除一次)
为了更好求解,我们加了一行和一列0
作者:powcai
链接:https://leetcode-cn.com/problems/range-sum-query-2d-immutable/solution/dong-tai-gui-hua-by-powcai-10/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
3.代码实现
class NumMatrix(object):
def __init__(self, matrix):
"""
:type matrix: List[List[int]]
"""
if not matrix or not matrix[0]:
return
row = len(matrix)
col = len(matrix[0])
self.dp = [[0] * (col + 1) for _ in range(row + 1)]
# 求行的前缀和
for i in range(1, row + 1):
for j in range(1 ,col + 1):
self.dp[i][j] = self.dp[i][j - 1] + matrix[i - 1][j - 1]
# 求列的前缀和
for j in range(1, col + 1):
for i in range(1, row + 1):
self.dp[i][j] += self.dp[i - 1][j]
def sumRegion(self, row1, col1, row2, col2):
"""
:type row1: int
:type col1: int
:type row2: int
:type col2: int
:rtype: int
"""
return self.dp[row2+1][col2+1] - self.dp[row2+1][col1] - self.dp[row1][col2+1] + self.dp[row1][col1]
# Your NumMatrix object will be instantiated and called as such:
# obj = NumMatrix(matrix)
# param_1 = obj.sumRegion(row1,col1,row2,col2)
题目四:lc308.二维区域和检索 - 可变
1.题目描述
给你一个 2D 矩阵 matrix,请计算出从左上角 (row1, col1) 到右下角 (row2, col2) 组成的矩形中所有元素的和。
上述粉色矩形框内的,该矩形由左上角 (row1, col1) = (2, 1) 和右下角 (row2, col2) = (4, 3) 确定。其中,所包括的元素总和 sum = 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
update(3, 2, 2)
sumRegion(2, 1, 4, 3) -> 10
注意:
矩阵 matrix 的值只能通过 update 函数来进行修改
你可以默认 update 函数和 sumRegion 函数的调用次数是均匀分布的
你可以默认 row1 ≤ row2,col1 ≤ col2
2.解题思路
那么为了能够通过OJ,我们还是需要用到树状数组Binary Indexed Tree(参见题目二),其查询和修改的复杂度均为O(logn),那么我们还是要建立树状数组,我们根据数组中的每一个位置,建立一个二维的树状数组,然后还需要一个getSum函数,以便求得从(0, 0)到(i, j)的区间的数字和,然后在求某一个区间和时,就利用其四个顶点的区间和关系可以快速求出
3.代码实现
class NumMatrix(object):
def __init__(self, matrix):
"""
:type matrix: List[List[int]]
"""
if not matrix or not matrix[0]:
return
m = len(matrix)
n = len(matrix[0])
self.mat = [[0 for _ in range(n)] for _ in range(m)]
self.bit = [[0 for _ in range(n+1)] for _ in range(m+1)]
for i in range(m):
for j in range(n):
self.update(i,j,matrix[i][j])
def update(self, row, col, val):
"""
:type row: int
:type col: int
:type val: int
:rtype: None
"""
diff = val - self.mat[row][col]
self.mat[row][col] = val
i = row+1
while i < len(self.bit):
# 注意j的初始化顺序
j = col+1
while j < len(self.bit[0]):
self.bit[i][j] += diff
j += j&(-j)
i += i&(-i)
#print self.mat
def getSum(self,row,col):
res = 0
i = row
while i > 0:
# 注意j的初始化顺序
j = col
while j > 0:
res += self.bit[i][j]
j -= j&-j
i -= i&-i
return res
def sumRegion(self, row1, col1, row2, col2):
"""
:type row1: int
:type col1: int
:type row2: int
:type col2: int
:rtype: int
"""
return self.getSum(row2+1, col2+1)-self.getSum(row1, col2+1)-self.getSum(row2+1, col1)+self.getSum(row1, col1)
# Your NumMatrix object will be instantiated and called as such:
# obj = NumMatrix(matrix)
# obj.update(row,col,val)
# param_2 = obj.sumRegion(row1,col1,row2,col2)