Given an integer array nums
, handle multiple queries of the following types:
nums
.nums
between indices left
and right
inclusive where left <= right
.Implement the NumArray
class:
NumArray(int[] nums)
Initializes the object with the integer array nums
.void update(int index, int val)
Updates the value of nums[index]
to be val
.int sumRange(int left, int right)
Returns the sum of the elements of nums
between indices left
and right
inclusive (i.e. nums[left] + nums[left + 1] + ... + nums[right]
).
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 * 104
-100 <= nums[i] <= 100
0 <= index < nums.length
-100 <= val <= 100
0 <= left <= right < nums.length
3 * 104
calls will be made to update
and sumRange
.This problem requires designing a data structure that supports efficient range sum queries and updates. Two common approaches are using a Binary Indexed Tree (BIT) or a Segment Tree. Both solutions are presented below.
A Binary Indexed Tree, also known as a Fenwick Tree, is a data structure that allows for efficient prefix sum queries and updates in O(log n) time. It achieves this by cleverly representing the prefix sums in an array.
Core Idea: Each element in the BIT array c
stores the sum of a range of elements in the original array nums
. The range covered by c[i]
depends on the binary representation of i
. Specifically, c[i]
represents the sum of elements from i - (i & -i) + 1
to i
. The expression i & -i
isolates the least significant bit of i
.
Update Operation: To update an element nums[index]
to val
, we calculate the difference between the new value and the old value (delta = val - nums[index]
). Then, we traverse the BIT array, adding delta
to all relevant elements.
Query Operation: To calculate the sum of elements from index left
to right
, we compute the prefix sum up to right + 1
and subtract the prefix sum up to left
.
Time Complexity:
Space Complexity: O(n) - linear space to store the BIT array.
A Segment Tree is a tree-based data structure where each node represents a range of elements in the original array. The tree is built recursively, with each internal node covering a range that is the union of its children's ranges.
Core Idea: The root node covers the entire array. Internal nodes are recursively divided to cover sub-ranges. Leaf nodes represent single elements. Each node stores the sum of the elements in its range.
Update Operation: To update an element, we traverse the tree from the root to the leaf node corresponding to the index. We update the value of the leaf node and then propagate the change upwards, updating the sums in the parent nodes.
Query Operation: To find the sum of elements in a range, we traverse the tree. If a node's range completely falls within the query range, we add its sum to the result. Otherwise, we recursively explore the left and right subtrees.
Time Complexity:
Space Complexity: O(4n) ≈ O(n) - linear space to store the segment tree (although it's 4 times the size of the input array in the worst case due to the way the tree is constructed).
Both approaches offer similar time and space complexities. The choice between them often depends on personal preference or other factors like the specific problem constraints. The Binary Indexed Tree (BIT) generally uses less memory. The Segment Tree's structure can be more easily extended to support other operations (e.g., range minimum/maximum queries). The provided code implements both methods for the given problem.