[Medium] 307. Range Sum Query - Mutable
Given an integer array nums, handle multiple queries of the following types:
- Update the value of an element in
nums. - Calculate the sum of the elements of
numsbetween indicesleftandrightinclusive whereleft <= right.
Implement the NumArray class:
NumArray(int[] nums)Initializes the object with the integer arraynums.void update(int index, int val)Updates the value ofnums[index]to beval.int sumRange(int left, int right)Returns the sum of the elements ofnumsbetween indicesleftandrightinclusive (i.e.nums[left] + nums[left + 1] + ... + nums[right]).
Examples
Example 1:
Input
["NumArray", "sumRange", "update", "sumRange"]
[[[1, 3, 5]], [0, 2], [1, 2], [0, 2]]
Output
[null, 9, null, 8]
Explanation
NumArray numArray = new NumArray([1, 3, 5]);
numArray.sumRange(0, 2); // return 1 + 3 + 5 = 9
numArray.update(1, 2); // nums = [1, 2, 5]
numArray.sumRange(0, 2); // return 1 + 2 + 5 = 8
Constraints
1 <= nums.length <= 3 * 10^4-100 <= nums[i] <= 1000 <= index < nums.length-100 <= val <= 1000 <= left <= right < nums.length- At most
3 * 10^4calls will be made toupdateandsumRange.
Thinking Process
- 0-Indexed vs 1-Indexed: This solution uses 0-indexed (left child =
2*node+1). 1-indexed uses2*nodeand2*node+1.
- Clarify if the array is sorted, has negatives, or allows duplicates.
- Prefix sums answer range queries; hash maps answer pair/count queries.
- In-place tricks use swap/write index instead of extra arrays.
Common Approaches
Typical techniques for this pattern:
| Approach | Time | Space | Notes |
|---|---|---|---|
| Prefix sum (this problem) | O(n) | O(n) | Range queries, subarray sum |
| Sort + scan | O(n log n) | O(1) | Intervals, meeting rooms |
| Kadane’s algorithm | O(n) | O(1) | Maximum subarray |
| Hash map counting | O(n) | O(n) | Frequency, two-sum variants |
Solution
Solution: Segment Tree (0-Indexed Array Representation)
class NumArray {
public:
NumArray(vector<int>& nums){
n = nums.size();
tree.resize(4 * n);
build(0, 0, n - 1, nums);
}
void update(int index, int val) {
update(0, 0, n - 1, index, val);
}
int sumRange(int left, int right) {
return (int)query(0, 0, n - 1, left, right);
}
private:
vector<long long> tree;
int n;
void build(int node, int l, int r, vector<int>& nums) {
if(l == r) {
tree[node] = nums[l];
return;
}
int mid = l + (r - l) / 2;
build(2 * node + 1, l, mid, nums);
build(2 * node + 2, mid + 1, r, nums);
tree[node] = tree[2 * node + 1] + tree[2 * node + 2];
}
void update(int node, int l, int r, int idx, int val) {
if(l == r){
tree[node] = val;
return;
}
int mid = l + (r - l) / 2;
if(idx <= mid){
update(2 * node + 1, l, mid, idx, val);
} else {
update(2 * node + 2, mid + 1, r, idx, val);
}
tree[node] = tree[2 * node + 1] + tree[2 * node + 2];
}
long long query(int node, int l, int r, int ql, int qr) {
if(qr < l || r < ql) return 0;
if(ql <= l && r <= qr) return tree[node];
int mid = l + (r - l) / 2;
return query(2 * node + 1, l, mid, ql, qr) + query(2* node + 2, mid + 1, r, ql, qr);
}
};
/**
* Your NumArray object will be instantiated and called as such:
* NumArray* obj = new NumArray(nums);
* obj->update(index,val);
* int param_2 = obj->sumRange(left,right);
*/
Solution Explanation
Approach: Prefix sum (this problem)
Key idea: 1. 0-Indexed vs 1-Indexed: This solution uses 0-indexed (left child = 2*node+1). 1-indexed uses 2*node and 2*node+1.
How the code works:
- 0-Indexed vs 1-Indexed: This solution uses 0-indexed (left child =
2*node+1). 1-indexed uses2*nodeand2*node+1.- Clarify if the array is sorted, has negatives, or allows duplicates.
- Prefix sums answer range queries; hash maps answer pair/count queries.
- In-place tricks use swap/write index instead of extra arrays.
Algorithm Explanation:
NumArray Class:
- Constructor (Lines 3-7):
- Initialize segment tree with size
4 * n - Build tree from
numsarray starting at root node (index 0)
- Initialize segment tree with size
- build() (Lines 15-25):
- Recursively build segment tree
- Base Case: Leaf node (
l == r) storesnums[l] - Recursive Case:
- Build left subtree:
2 * node + 1for range[l, mid] - Build right subtree:
2 * node + 2for range[mid + 1, r] - Parent node stores sum of children:
tree[node] = tree[left] + tree[right]
- Build left subtree:
- update() (Lines 5-6, 27-37):
- Public method delegates to private recursive method
- Update element at index
idxto valueval - Base Case: Leaf node (
l == r) → update directly - Recursive Case:
- Navigate to appropriate child based on
idx <= mid - Update child subtree
- Recalculate parent:
tree[node] = tree[left] + tree[right]
- Navigate to appropriate child based on
- query() (Lines 8-9, 39-45):
- Public method delegates to private recursive method
- Query sum over range
[ql, qr] - No Overlap:
qr < l || r < ql→ return 0 - Complete Overlap:
ql <= l && r <= qr→ returntree[node] - Partial Overlap: Query both children and sum results
Tree Structure (0-Indexed):
For array [1, 3, 5]:
[9] ] ← node 0
/ \
[4] [5] ← nodes 1, 2
/ \ / \
[1] [3] [5] [0] ← nodes 3, 4, 5, 6 (leaves)
0 1 2 3 ← array indices
Node Indexing:
- Root:
node = 0 - Left child:
2 * node + 1 - Right child:
2 * node + 2 - Parent:
(node - 1) / 2(if node > 0)
Example Walkthrough:
Input: nums = [1, 3, 5]
Step 1: Build Tree
build(0, 0, 2, [1, 3, 5]):
- Left: build(1, 0, 1, ...)
- Left: build(3, 0, 0, ...) → tree[3] = 1
- Right: build(4, 1, 1, ...) → tree[4] = 3
- tree[1] = tree[3] + tree[4] = 1 + 3 = 4
- Right: build(2, 2, 2, ...) → tree[5] = 5
- tree[0] = tree[1] + tree[2] = 4 + 5 = 9
Step 2: Query [0, 2]
query(0, 0, 2, 0, 2):
- Complete overlap → return tree[0] = 9 ✓
Step 3: Update index 1 to 2
update(0, 0, 2, 1, 2):
- idx=1 <= mid=1 → go left
- update(1, 0, 1, 1, 2):
- idx=1 > mid=0 → go right
- update(4, 1, 1, 1, 2):
- Leaf → tree[4] = 2
- tree[1] = tree[3] + tree[4] = 1 + 2 = 3
- tree[0] = tree[1] + tree[2] = 3 + 5 = 8
Step 4: Query [0, 2]
query(0, 0, 2, 0, 2):
- Complete overlap → return tree[0] = 8 ✓
Complexity Analysis:
- Time Complexity:
- Build: O(n) - Visit each element once
- Update: O(log n) - Traverse from root to leaf
- Query: O(log n) - Traverse tree height
- Overall: O(n) build + O(k log n) for k operations
- Space Complexity: O(4n) = O(n)
- Segment tree array:
4 * n(worst case) - Recursion stack: O(log n)
- Overall: O(n)
Common Mistakes
- Segment tree array:
- Single element:
nums = [5]→ tree stores single value - Negative numbers:
nums = [-1, -2, -3]→ sum works correctly - Large array: Up to 30,000 elements → segment tree handles efficiently
- Many queries: Up to 30,000 queries → O(log n) per query is essential
-
Update same index multiple times: Each update is independent
- Wrong array size: Using
2 * ninstead of4 * n→ index out of bounds - Index calculation errors: Wrong child indices (off-by-one)
- Not updating parent: Forgetting to recalculate parent after update
- Query boundary errors: Incorrect overlap checking logic
- Integer overflow: Not using
long longfor large sums
Related Problems
- LC 303: Range Sum Query - Immutable - Prefix sum (no updates)
- LC 308: Range Sum Query 2D - Mutable - 2D segment tree
- LC 850: Rectangle Area II - Segment tree with coordinate compression
- LC 3477: Number of Unplaced Fruits - Segment tree for leftmost query
- LC 699: Falling Squares - Segment tree for range max updates
Key Takeaways
- 0-Indexed vs 1-Indexed: This solution uses 0-indexed (left child =
2*node+1). 1-indexed uses2*nodeand2*node+1. - Array Size: Allocate
4 * nto handle worst-case tree structure - Long Long: Use
long longto prevent integer overflow for large sums - Recursive vs Iterative: Recursive is cleaner; iterative can avoid stack overflow
- Range Query Logic: Three cases: no overlap, complete overlap, partial overlap
References
- LC 307: Range Sum Query - Mutable on LeetCode
- LeetCode Discuss — LC 307: Range Sum Query - Mutable
- LeetCode Editorial (may require premium)