一、题目描述
给你一个数组 nums
,请你完成两类查询。
- 其中一类查询要求 更新 数组
nums
下标对应的值 - 另一类查询要求返回数组
nums
中索引left
和索引right
之间( 包含 )的nums元素的 和 ,其中left <= right
实现 NumArray
类:
NumArray(int[] nums)
用整数数组nums
初始化对象void update(int index, int val)
将nums[index]
的值 更新 为val
int sumRange(int left, int right)
返回数组nums
中索引left
和索引right
之间( 包含 )的nums元素的 和 (即,nums[left] + nums[left + 1], ..., nums[right]
)
示例 1:
输入: ["NumArray", "sumRange", "update", "sumRange"] [[[1, 3, 5]], [0, 2], [1, 2], [0, 2]] 输出: [null, 9, null, 8]解释: NumArray numArray = new NumArray([1, 3, 5]); numArray.sumRange(0, 2); // 返回 1 + 3 + 5 = 9 numArray.update(1, 2); // nums = [1,2,5] numArray.sumRange(0, 2); // 返回 1 + 2 + 5 = 8
提示:
1 <= nums.length <= 3 * 10^4
-100 <= nums[i] <= 100
0 <= index < nums.length
-100 <= val <= 100
0 <= left <= right < nums.length
- 调用
update
和sumRange
方法次数不大于3 * 10^4
二、解题思路
这个问题可以通过使用一个数据结构来优化区间和查询的效率,这个数据结构通常被称为树状数组(Binary Indexed Tree, BIT)或者线段树(Segment Tree)。由于树状数组在实现上较为简洁,我们这里采用树状数组来解决问题。
解题思路:
- 使用一个额外的数组
tree
来构建树状数组,其中tree[i]
用于存储从nums[i - 2^r + 1]
到nums[i]
的元素和,这里r
是i
二进制表示中最低位的1
所在的位置。 - 在
NumArray
构造函数中初始化树状数组。 - 在
update
方法中,首先计算出需要更新的元素的新值与旧值的差值,然后更新nums
数组中对应位置的值,接着更新树状数组。 - 在
sumRange
方法中,计算从left
到right
的区间和。
三、具体代码
class NumArray {private int[] nums;private int[] tree;private int n;public NumArray(int[] nums) {this.nums = nums;this.n = nums.length;this.tree = new int[n + 1];for (int i = 0; i < n; i++) {add(i + 1, nums[i]);}}public void update(int index, int val) {int delta = val - nums[index];nums[index] = val;add(index + 1, delta);}public int sumRange(int left, int right) {return query(right + 1) - query(left);}private void add(int index, int val) {while (index <= n) {tree[index] += val;index += index & -index;}}private int query(int index) {int sum = 0;while (index > 0) {sum += tree[index];index -= index & -index;}return sum;}
}
四、时间复杂度和空间复杂度
1. 时间复杂度
-
构造函数
NumArray(int[] nums)
:- 初始化树状数组的时间复杂度是
O(n)
,因为我们需要遍历数组nums
并对每个元素调用一次add
方法。 add
方法的时间复杂度是O(log n)
,因为每次更新树状数组时,我们只需要更新log n
个节点。- 因此,构造函数总的时间复杂度是
O(n * log n)
。
- 初始化树状数组的时间复杂度是
-
update(int index, int val)
方法:- 计算差值
delta
的时间复杂度是O(1)
。 - 更新
nums
数组的时间复杂度是O(1)
。 - 调用
add
方法的时间复杂度是O(log n)
。 - 因此,
update
方法总的时间复杂度是O(log n)
。
- 计算差值
-
sumRange(int left, int right)
方法:- 调用两次
query
方法,每次的时间复杂度是O(log n)
。 - 因此,
sumRange
方法总的时间复杂度是O(log n)
。
- 调用两次
2. 空间复杂度
-
构造函数
NumArray(int[] nums)
:- 需要一个与
nums
同样长度的数组tree
来存储树状数组,因此空间复杂度是O(n)
。 nums
数组本身也需要O(n)
的空间。- 因此,构造函数总的空间复杂度是
O(n)
。
- 需要一个与
-
update(int index, int val)
方法:- 这个方法不需要额外的空间,除了修改
nums
和tree
数组中已有的元素,因此空间复杂度是O(1)
。
- 这个方法不需要额外的空间,除了修改
-
sumRange(int left, int right)
方法:- 这个方法同样不需要额外的空间,只需要计算两个
query
方法的差值,因此空间复杂度是O(1)
。
- 这个方法同样不需要额外的空间,只需要计算两个
综上所述,整个 NumArray
类的时间复杂度和空间复杂度如下:
-
时间复杂度:
- 构造函数:
O(n * log n)
update
方法:O(log n)
sumRange
方法:O(log n)
- 构造函数:
-
空间复杂度:
- 总空间复杂度:
O(n)
- 总空间复杂度:
五、总结知识点
-
树状数组(Binary Indexed Tree, BIT):
- 树状数组是一种用于高效计算和更新前缀和的数据结构。
- 它通过使用数组来模拟树形结构,使得每个节点的父节点可以通过位运算快速定位。
-
位运算:
- 代码中使用了位运算
index & -index
来找到当前节点的父节点,这是树状数组的一个关键特性。 - 该操作能够提取出
index
二进制表示中最低位的 1,从而实现快速更新和查询。
- 代码中使用了位运算
-
前缀和:
query
方法通过累加树状数组中的值来计算前缀和,这是树状数组的核心功能之一。
-
数组操作:
- 在
update
方法中,通过计算新旧值的差值来更新树状数组,而不是直接替换,这样可以保持树状数组的正确性。
- 在
-
类的封装:
NumArray
类封装了树状数组的操作,对外提供了update
和sumRange
两个接口,隐藏了实现的细节。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。