307. Range Sum Query - Mutable

问题:

给定一个数组,实现方法:

sumRange(i, j):求出第i个元素到第j个元素的和

update(i, val):更新第i个元素的值为val

Example:
Given nums = [1, 3, 5]
sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8
 

Constraints:

The array is only modifiable by the update function.
You may assume the number of calls to update and sumRange function is distributed evenly.
0 <= i <= j <= nums.length - 1

  

解法:

解法一:FenwickTree

利用FenwickTree数据结构,构建一个数组的 “lowbit选择出的,连续元素和”(partsum) 底层数据结构。

直接调用接口函数,

  • 更新某位置元素:update(i, diff)
  • 获取前缀和:PreSum(i)

 

代码参考:

class FenwickTree {
public:
    FenwickTree(int n):partsum(n+1, 0){
        
    }
    void update(int i, int delta){
        while(i<partsum.size()){
            partsum[i]+=delta;
            i+=lowbit(i);
        }
    }
    int PreSum(int i){
        int sum=0;
        while(i>0){
            sum+=partsum[i];
            i-=lowbit(i);
        }
        return sum;
    }
private:
    vector<int> partsum;
    int lowbit(int x){
        return x & (-x);
    }
};

class NumArray {
public:
    NumArray(vector<int>& nums):num_(move(nums)), tree(nums.size()) {
        for(int i=0; i<num_.size(); i++){
            tree.update(i+1, num_[i]);
        }
    }
    
    void update(int i, int val) {
        int diff=val-num_[i];
        tree.update(i+1, diff);
        num_[i]=val;
    }
    
    int sumRange(int i, int j) {
        return tree.PreSum(j+1)-tree.PreSum(i);
    }
private:
    FenwickTree tree;
    vector<int> num_;
};

/**
 * Your NumArray object will be instantiated and called as such:
 * NumArray* obj = new NumArray(nums);
 * obj->update(i,val);
 * int param_2 = obj->sumRange(i,j);
 */

 

解法二:Segment Tree

(线段树)

定义TreeNode有以下变量:

  • int start_idx:该节点所代表的范围:起始
  • int end_idx:该节点所代表的范围:终止
  • int val:该节点的值(将来要合并的内容)
  • TreeNode *left:左子节点
  • TreeNode *right:右子节点

关键方法皆使用 递归 方法,进行构建。

1. 创建树:

  • Param:起始范围
  • return:树根节点
  • 递归退出条件:范围指向一个点。start==end,则直接由该点值val新建TreeNode返回。
  • 一般情况:由范围中点mid,对半分类讨论。最终左右合并到的共同父节点,需要新建TreeNode
1     SegmentTreeNode* buildTree(int start, int end){
2         if(start==end){
3             return new SegmentTreeNode(start, end, num_[start], nullptr, nullptr);
4         }
5         int mid = start + (end-start)/2;
6         SegmentTreeNode* left=buildTree(start, mid);
7         SegmentTreeNode* right=buildTree(mid+1, end);
8         return new SegmentTreeNode(start, end, left->val+right->val, left, right);
9     }

 

2. 更新树节点:

  • Param:当前树根节点:root,节点index:i,要更新到的值:val
  • return:void,不需要返回值
  • 递归退出条件:当前树根节点范围指向一个点:i。root->start==root->end==i,则直接更新该点值root->val,返回。
  • 一般情况:由范围中点mid,对半分类讨论,更新完毕。最终,需要更新左右共同父节点的值,root->val=left->val+right->val。
 1     void updateTree(SegmentTreeNode* root, int i, int val){
 2         if(root->start == i && root->end == i){
 3             root->val=val;
 4             return;
 5         }
 6         int mid = root->start + (root->end-root->start)/2;
 7         if(i<=mid) updateTree(root->left, i, val);
 8         else updateTree(root->right, i, val);
 9         root->val = root->left->val + root->right->val;
10     }

3. 求某段范围val合并值:

  • Param:当前树根节点:root,要求范围:i~j
  • return:合并值结果。
  • 递归退出条件:当前树根节点范围root->start~root->end刚好==i~j。root->start==i && root->end==j,则直接返回该点值root->val,返回。
  • 一般情况:由范围中点mid,对半分类讨论:
    • i~j全落在root->left:直接返回,递归调用本函数。root传入root->left
    • i~j全落在root->right:直接返回,递归调用本函数。root传入root->right
    • i~j分别落在root->left和root->right:返回 落在left的结果+落在right的结果:(当前树根节点:root->left, 范围:i~mid)+(当前树根节点:root->right, 范围:mid+1~j)
1     int getSum(SegmentTreeNode* root, int i, int j){
2         if(i==root->start && j==root->end){
3             return root->val;
4         }
5         int mid = root->start + (root->end-root->start)/2;
6         if(j<=mid) return getSum(root->left, i, j);
7         else if(i>mid) return getSum(root->right, i, j);
8         else return getSum(root->left, i, mid)+getSum(root->right, mid+1, j);
9     }

代码参考:

class SegmentTreeNode {
public:
    SegmentTreeNode(int start, int end, int val,
                    SegmentTreeNode *left,
                    SegmentTreeNode *right):
    start(start),
    end(end),
    val(val),
    left(left),
    right(right){}
    
    int start;
    int end;
    int val;
    SegmentTreeNode *left;
    SegmentTreeNode *right;
};

class NumArray {
public:
    NumArray(vector<int>& nums):num_(move(nums)) {
        if(!num_.empty()) treeroot = buildTree(0, num_.size()-1);
    }
    
    void update(int i, int val) {
        updateTree(treeroot, i, val);
        num_[i]=val;
    }
    
    int sumRange(int i, int j) {
        return getSum(treeroot, i, j);
    }
private:
    SegmentTreeNode* treeroot;
    vector<int> num_;
    
    SegmentTreeNode* buildTree(int start, int end){
        if(start==end){
            return new SegmentTreeNode(start, end, num_[start], nullptr, nullptr);
        }
        int mid = start + (end-start)/2;
        SegmentTreeNode* left=buildTree(start, mid);
        SegmentTreeNode* right=buildTree(mid+1, end);
        return new SegmentTreeNode(start, end, left->val+right->val, left, right);
    }
    void updateTree(SegmentTreeNode* root, int i, int val){
        if(root->start == i && root->end == i){
            root->val=val;
            return;
        }
        int mid = root->start + (root->end-root->start)/2;
        if(i<=mid) updateTree(root->left, i, val);
        else updateTree(root->right, i, val);
        root->val = root->left->val + root->right->val;
    }
    int getSum(SegmentTreeNode* root, int i, int j){
        if(i==root->start && j==root->end){
            return root->val;
        }
        int mid = root->start + (root->end-root->start)/2;
        if(j<=mid) return getSum(root->left, i, j);
        else if(i>mid) return getSum(root->right, i, j);
        else return getSum(root->left, i, mid)+getSum(root->right, mid+1, j);
    }
};

/**
 * Your NumArray object will be instantiated and called as such:
 * NumArray* obj = new NumArray(nums);
 * obj->update(i,val);
 * int param_2 = obj->sumRange(i,j);
 */

 

posted @ 2020-08-01 17:34  habibah_chang  阅读(107)  评论(0编辑  收藏  举报