区域和检索 - 数组可修改(2022-4-4)每日一练

307. 区域和检索 - 数组可修改(2022-4-4)

给你一个数组 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
  • 调用 pdate 和 sumRange 方法次数不大于 3 * 10^4

解题思路

这道题要直接遍历,肯定超时,不用想我帮你们试过了。

然后我就想到了前缀和的方式,将sunRange转化为*O(1)*的方法,直接用sums[right] - sums[left-1]去求和。

至于update我选择使用一个change存储变化量。在求和时,先遍历一遍「变化」计算出与最初数组的差值,然后再加上前缀和求出来的结果。我自以为这个方案是可行的。世事难料!又超时了!淦!

代码长这个样子:

var NumArray = function(nums) {
    this._arr = nums // 正常数组
    this.arr = new Array(nums.length) // 前缀和数组
    this.arr[0] = nums[0]
    for(let i = 0; i< this.arr.length; i++){
        this.arr[i] = this.arr[i-1] + nums[i]
    }
    this.change = new Map()
};
NumArray.prototype.update = function(index, val) {
    let change = val - this._arr[index]
    this._arr[index] = val
    this.change.set(index,(this.change.get(index) || 0) + change)
};
NumArray.prototype.sumRange = function(left, right) {
    let sum = 0
    // 先求出到目前为止的变化量
    for(let [index,change] of this.change.entries()){
        if(index >= left && index <= right) sum += change
    }
    if(left == 0 ) sum = sum + this.arr[right]
    else sum = sum + this.arr[right] - this.arr[left-1]
    return sum
};

之后就去参考了下别人的题解,见识了从没听过的数据结构和方法「树状数组」。这里是一篇讲解详细的题解。

其实大致的思想和上述差不多,也是利用前缀和,不过使用的结构是「树状数组」而上文使用的是普通线性数组,在查找效率以及更新效率上是不如树状的。

总而言之,我认为是更换了一种遍历方式,从「1、2、3……」到「1、2、1、4、1、6……」;当然看不懂这个遍历方式很正常,因为是树状的,通过x & -x得出的;具体理解可见上边的题解,还有图解,我就不献丑了。

上代码:

// 不一样的遍历方式,「树状数组」
function lowbit(x){
     return x & (-x)
 }
// 更新前缀和数组(也可以初始化)
 function insert (index, val, arr){
     let x = index + 1
     while(x < arr.length){
         arr[x] = arr[x] + val
         x += lowbit(x)
     }
     return arr
 }
// 查询当前索引的前缀和
 function query(x,sums){
     let s = 0
     while(x != 0) {
         s += sums[x]
         x -= lowbit(x)
     }
     return s
 }

var NumArray = function(nums) {
    this._arr = nums // 正常数组
    this.arr = new Array(nums.length + 1).fill(0) // 树状前缀和数组
    for(let i = 0; i< this.arr.length; i++){
      // 初始化前缀和数组,(因为写的独立方法,所以传参有点多)
        this.arr = insert(i, this._arr[i], this.arr)
    }
};

/** 
 * @param {number} index 
 * @param {number} val
 * @return {void}
 */
NumArray.prototype.update = function(index, val) {
    let x = index + 1
    while ( x < this.arr.length){ // 将更新应用到与之相关的数组项
        this.arr[x] = this.arr[x] - this._arr[index] + val
        x += lowbit(x)
    }
    this._arr[index] = val // 更新正常数组的值
};

/** 
 * @param {number} left 
 * @param {number} right
 * @return {number}
 */
NumArray.prototype.sumRange = function(left, right) {
    return query(right + 1,this.arr) - query(left,this.arr)
};

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/range-sum-query-mutable
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

你可能感兴趣的:(每日一练,前缀和,树状数组,区间和,leetcode,javascript)