[ 树状数组 ] 307. 区域和检索 - 数组可修改

159 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第8天,点击查看活动详情

每日刷题 2021.04.08

题目

  • 给你一个数组 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])

示例

输入:
["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
  • 调用 updatesumRange 方法次数不大于 3 * 10^4 

解题思路

注意

  • 想要在类中自定义方法,需要这样写NumArray.prototype.lowbit,直接将其添加到原型对象上,才能使用this调用。

思路

  • lowbit函数:lowbit操作用于得到该数二进制分解的最小的2的次幂(即二进制数的最后一个1)
  • 例如 lowbit(2) = 2, 2(0010)二进制分解的最小的2的次幂是21次方(0010)
    • lowbit(5) = 1, 5(1001)二进制分解的最小的2的次幂是1的一次方(0001)
    • lowbit函数的实现:显而易见,一个数按位取反+1再并上本身就可以实现lowbit函数,恰好有符号数中负数的表示(补码)就是该数按位取反+1.
  • 暴力解法就是, 维护一个前缀和数组, 当更新某个的元素的值时,计算出变化量,给需要更新的前缀和元素进行更新。
  • 看代码题解的前提是知道什么是线段树,本题目解答就不放入图片了,就是树的每个节点包含了start,end 数组的区间,以及区间和 sum。还有left左节点,right右节点。
    • 首先定义一个TreeNode类,代码中定义的首字母小写了不过不影响
    • 然后分别定义NumArray类的方法,分别为生成线段树,也就是this.root = this.buildTree(nums,0,nums.length-1)
    • 定义update更新方法, 定义sum求和方法

AC代码

var NumArray = function(nums) {
  this.nums = nums;
  this.len = nums.length;
  this.tree = new Array(this.len + 1).fill(0);
  for(let i = 1; i <= this.len; i++) {
    this.update(i - 1, this.nums[i - 1]);
  }
};

NumArray.prototype.lowbit = function (x) {
  return x & (-x);
}

NumArray.prototype.getSum = function (right) {
  let res = 0;
  while (right > 0) {
    res += this.tree[right];
    right -= this.lowbit(right);
  }
  return res;
}

/** 
 * @param {number} index 
 * @param {number} val
 * @return {void}
 */
NumArray.prototype.update = function(index, val) {
  // 原有的
  index++;
  let cur = this.getSum(index) - this.getSum(index - 1);
  let k = val - cur;
  while(index <= this.len) {
    this.tree[index] += k;
    index += this.lowbit(index)
  }
};

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

总结

  • 初次学习树状数组,有参考一些大佬的题解和视频,帮助很大