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); */