Range Sum Problems
Introduction
There are four similar problems about range sum in leetcode: 303. Range Sum Query - Immutable Immutable version doesn’t have update operation. 307. Range Sum Query - Mutable Given an integer array nums, find the sum of the elements between indices i and j (i ≤ j), inclusive. The update(i, val) function modifies nums by updating the element at index i to val. 1
2
3
4
5Given nums = [1, 3, 5]
sumRange(0, 2) -> 9
update(1, 2)
sumRange(0, 2) -> 8
The above rectangle (with the red border) is defined by (row1, col1) = (2, 1) and (row2, col2) = (4, 3), which contains sum = 8.
Immutable Range Sum
Immutable Range Sum (2D) problem can be solved by accumulating sum from index 0 to current index i in an additional array sum[], then it takes only O(1) time to calculate sum from i to j by simply doing sum[j] - sum[i-1]. The pre-computation of sum[] array in constructor takes O(N) time and never changes over time as there is no update operation.
For 2D Sum Range Problem, the basic idea is the same and we create an additional matrix to store sum in area (0, 0, i, j). Dynamic Programming can be applied here to avoid repeatedly calculate sum in previous areas.
1 | /* It takes O(1) to sum but O(N) to update by pre-computing sum from |
Mutable Range Sum
The naive method to solve Immutable Range Sum doesn’t apply here as update operation will take O(N) time and make overall time complexity O(N) because update and sum operations are equally distributed.
Some data structure like Segment Tree and Binary Indexed Tree can have performance gain and make both update and sum operations to O(log(N)) with a little memory overhead.
Segment Tree
Segment Tree is basically a binary tree, in which the left node is left half subarray, the right node is right half subarray and the value of each node is the sum of left and right subarray. The root node contains the whole input array and we can recursively parse an array into two subarrays until the leaf node only contains one element.
Obviously it takes O(log(N)) time to update the value by adding the difference between old value and new value into corresponding segments in each level of the segment tree. If index i is about to be updated and i is between start and end of current node, then updateNode() method will add difference to the value of current node and recursively update its left and right node with same range check and update.
Sum operation is a little harder to understand than update operation. It searches from starting root node with O(log(N)) time and has three conditions below: * Query range is completely outside of node range, then return 0. * Node range is within query range, then return the value of this node. * Node range and query range is overlapped, then recursively search and sum on both left and right nodes with same range parameters i and j.
1 | /********************** Segment Tree *****************************/ |
Binary Indexed Tree
Binary Indexed Tree also known as Fenwick Tree is a data structure used to efficiently update elements and calculate prefix sums in number tables with O(log(N)) on both update and sum operations. It only uses an additional array with same length of input array to store its data structure and code of its implementation is deadly simple and short!
The basic idea is that any integer can be written in binary form as the sum of powers of 2. For example, 13 = 2^3 + 2^2 + 2^0. Let’s define input array as S[] and sum array as T[]. For each index idx in array S[], r is the position in binary form idx of the last digit 1 from left to right. In our example 13 = 1101, r = 3 and T[idx] is responsible for sums from 0 to idx - 2^r - 1 in S[] array, so S[0] + ... + S[1101] = T[1101] + T[1100] + T[1000]. The strategy here is to find the last digit 1, replace this digit with 0 and get the new index until the new index is all zeros. idx -= (idx & -idx) is used to isolate last digit 1 and get the next index in an amazingly simple way with less than 10 lines of code.
Article Reference from TopCoder
1 | class BinaryIndexedTreeSumQuery { |
Move from 1D to 2D
This follow up question requires us to extend 1d Binary Indexed Tree to 2D version in the same manner. When we update position (i, j) with new value val. In inner while loop, consider each row as 1d Binary Indexed Tree and several positions at this row are updated with val starting from index j and backward to beginning. In outer while loop, index i decides which rows are updated as 1D Binary Indexed Tree starting from index i and backward to beginning as well. A read() function is needed to sum area from (0, 0) to (i, j) and this function is then used to to sum a random area by cropping out side areas from main area and adding overlapped area.
1 | public class RangeSumQuery2DMutable { |