leetcode:307. 区域和检索 - 数组可修改

题目来源

题目描述

在这里插入图片描述
在这里插入图片描述

class NumArray {
public:
    NumArray(vector<int>& nums) {

    }
    
    void update(int index, int val) {

    }
    
    int sumRange(int left, int right) {

    }
};

题目解析

本次要解决的问题是:单点修改+区间查询(区间和)

线段树

线段树是一个二叉树,每个节点保存数组 n u m s nums nums在区间 [ s , e ] [s,e] [se]的最小值、最大值或者总和等信息。

线段树可以用数组或者树来实现。

对于数组实现,假设根节点下标为0,如果一个节点在数组的下标是 n o d e node node,那么它的左节点为node * 2 + 1,右节点下标为node。

  • 建立build函数

    • 我们在节点node保存数组nums在区间[s,e]的总和
      • s == e时,节点node是叶子节点,它保存的值等于nums[s]
      • s < e时,节点node的左子节点保存[s,mid]的总和,右子节点保存区间[mid+1,e]的总和,那么节点node保存的值等于它的两个子节点保存的值之和
    • 假设nums的大小为n,我们规定根节点node = 0保存区间[0,n-1]的总和,然后自下而上递归的建树
  • 单点修改change函数

    • 当我们要修改nums[idx]的值时,我们先找到对应区间[idx,idx]的叶子节点,直接修改叶子节点的值为val,然后自下而上递归的更新父节点的值
  • 范围求和range函数

    • 给定区间[left,right]时,我们将区间[left,right]拆分成多个节点对应的区间
      • 如果节点node对应的区间与[left,right]相同,那么直接返回该节点的值,即当前区间和
      • 如果节点node对应的区间与[left,right]不同,那么将区间拆分成[left,mid],[mid+1,right]两个区间,分别计算左子结点和右子结点。
class NumArray{
private:
    std::vector<int> segmentTree;
    int N;
    
    void build(int node, int left, int right, std::vector<int> &nums){
        if(left == right){
            segmentTree[node] = nums[left];
            return;
        }
        
        int mid = (left + right) >> 1;
        build(node * 2 + 1, left, mid, nums);
        build(node * 2 + 2, mid + 1, right, nums);
        segmentTree[node] = segmentTree[node * 2 + 1] + segmentTree[node * 2 + 2];
    }
    
    void change(int idx, int val, int node, int left, int right){
        if(left == right){
            segmentTree[node] = val;
            return;
        }

        int mid = (left + right) >> 1;
        if(idx <= mid){
            change(idx, val, node, left, mid);
        }else{
            change(idx, val, node, mid + 1, right);
        }
        segmentTree[node] = segmentTree[node * 2 + 1] + segmentTree[node * 2 + 2];
    }
    
    int range(int l, int r, int node, int left, int right){
        if(l == left && r == right){
            return segmentTree[node];
        }

        int mid = (left + right) >> 1;
        if(r <= mid){
            return range(l, r, node, left, mid);
        }else if(mid + 1 <= l){
            return range(l, r, node, mid + 1, right);
        }else{
            return range(l, r, node, left, mid) + range(l, r, node, mid + 1, right);
        }
    }

public:
    NumArray(std::vector<int> & nums) : N(nums.size()), segmentTree(nums.size() * 4){
        build(0, 0, N - 1, nums);      
    }
    
    void update(int idx, int val){
        change(idx, val, 0, 0, N - 1);
    }
    
    void sumRange(int l, int r){
        range(l, r, 0, 0, N - 1);
    }
};

在这里插入图片描述

懒更新

树状数组

树状数组是一种可以动态维护序列前缀和的数据结构(下标从1开始),它的功能是:

  • 单点修改add(idx, val):将序列第idx个数的值更新val
  • 区间查询prefixSum(idx):查询前idx个元素的前缀和

因为题目要求实现更新nums在某个位置的值,因此我们保存原始的nums数组

  • 构造函数:树状数组初始对应一个零序列,因此我们遍历nums数组,调用add函数来更新树状数组
  • update函数:
    • 获取 nums 在 idx 的增加值, 调用add 函数更新树状数组,并更nums[idx]=val。
  • sumRange 函数
    • 区间和[left,right]可以转化为两个前缀和之差
class NumArray{
private:
    std::vector<int> tree;
    std::vector<int> &nums;
    
    int lowBits(int idx){
        return idx &(-idx);
    }
    
    void add(int idx, int val){
        while (idx < tree.size()){
            tree[idx] += val;
            idx += lowBits(idx);
        }
    }
    
    int prefixSum(int idx){
        int sum = 0;
        while (idx > 0){
            sum += tree[idx];
            idx -= lowBits(idx);
        }
        return sum;
    }
    
public:
    NumArray(std::vector<int> & nums) : tree(nums.size() + 1), nums(nums){
        for (int i = 0; i < nums.size(); ++i) {
            add(i + 1, nums[i]);
        }
    }
    
    void update(int idx, int val){
        add(idx + 1, val - nums[idx]);
        nums[idx] = val;
    }
    
    int sumRange(int l, int r){
        return prefixSum(r + 1) - prefixSum(l);
    }
};

在这里插入图片描述

在这里插入图片描述
前置知识:
在这里插入图片描述

  • 该操作含义:计算x最低位的1在哪个位置。假设其计算结果是val
  • 其结构val可用的含义:
    • 数x在树状数组中所“覆盖的长度”(包括它自己在内)
    • 树x的父节点为x + val
    • 数x的前一个覆盖节点为x - val

比如对于lowBit(4) 来说,4→0100 ,−4→1100 (补码形式), 其 val就为0100→4,所以:

  • 覆盖长度为:4
  • 其父节点为:4 + 4 = 8
  • 其前一个覆盖的节点为:4-4=0
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值