The prefix sum approach is not so good of an idea now since updating one value will result in updating all the prefix sum after that index. We want a structure that's easy to make updates.
Behold, Segment Tree
The data variable in the segment tree will hold the sum value. Then, we need build the segment tree off the given array. Query and modify should be easy to implement after constructing the tree.
Segment Tree Code
Definition
private class Interval {
int ST, ED;
public Interval(int ST, int ED) {
this.ST = ST;
this.ED = ED;
}
}
private class STNode {
STNode leftChild, rightChild;
Interval inv;
long sum;
public STNode(Interval inv, long sum) {
this.inv = inv;
this.sum = sum;
}
}
Build
private STNode root;
public Solution(int[] A) {
// init data structure
if (A == null || A.length == 0) {
root = null;
} else {
root = build(A, new Interval(0, A.length-1));
}
}
private STNode build(int[] A, Interval inv) {
// create new node
STNode node = new STNode(inv, 0L);
// base case
if (inv.ST == inv.ED) {
node.sum = A[inv.ST];
return node;
}
// divide
int MI = inv.ST + (inv.ED - inv.ST) / 2;
Interval LInv = new Interval(inv.ST, MI);
Interval RInv = new Interval(MI+1, inv.ED);
STNode leftSubtree = build(A, LInv);
STNode rightSubtree = build(A, RInv);
// merge
node.leftChild = leftSubtree;
node.rightChild = rightSubtree;
node.sum = leftSubtree.sum + rightSubtree.sum;
return node;
}
Range Query
/**
* Return the range sum from index start to index end
*/
public long query(int start, int end) {
if (root == null || end < start) {
return -1L;
}
return rangeQuery(root, new Interval(start, end));
}
private long rangeQuery(STNode node, Interval query) {
// base case
if (node.inv.ST == query.ST && node.inv.ED == query.ED) {
return node.sum;
}
// divide
int MI = node.inv.ST + (node.inv.ED - node.inv.ST) / 2;
long leftSubtree = 0L;
long rightSubtree = 0L;
if (query.ED <= MI) { // search left
leftSubtree = rangeQuery(node.leftChild, query);
} else if (query.ST > MI) { // search right
rightSubtree = rangeQuery(node.rightChild, query);
} else { // search both
Interval leftQuery = new Interval(query.ST, MI);
Interval rightQuery = new Interval(MI+1, query.ED);
leftSubtree = rangeQuery(node.leftChild, leftQuery);
rightSubtree = rangeQuery(node.rightChild, rightQuery);
}
// merge
return leftSubtree + rightSubtree;
}
Update Query
/**
* Update A[index] to the given value
*/
public void modify(int index, int value) {
if (root == null || index < 0) {
return;
}
update(root, index, value);
}
private void update(STNode node, int updateIndex, int updateValue) {
// base case
if (node.inv.ST == updateIndex && node.inv.ED == updateIndex) {
node.sum = updateValue;
return;
}
// divide (search left or right)
int MI = node.inv.ST + (node.inv.ED - node.inv.ST) / 2;
if (updateIndex <= MI) { // search left
update(node.leftChild, updateIndex, updateValue);
} else { // search right
update(node.rightChild, updateIndex, updateValue);
}
// merge (update parent)
node.sum = node.leftChild.sum + node.rightChild.sum;
}
Time & Space Complexity
The segment tree will still take O(n) extra space. But the range queries and update queries only take O(logn) time.