一、题目描述
给你一个数组 nums ,请你完成两类查询。
- 其中一类查询要求 更新 数组
nums下标对应的值 - 另一类查询要求返回数组
nums中索引left和索引right之间( 包含 )的nums元素的 和 ,其中left <= right
实现 NumArray 类:
NumArray(int[] nums)用整数数组nums初始化对象void update(int index, int val)将nums[index]的值 更新 为valint 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] <= 1000 <= index < nums.length-100 <= val <= 1000 <= 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两个接口,隐藏了实现的细节。
以上就是解决这个问题的详细步骤,希望能够为各位提供启发和帮助。