Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Move from brute-force thinking to an efficient approach using array strategy.
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] <= 1000 <= index < nums.length-100 <= val <= 1000 <= left <= right < nums.length3 * 104 calls will be made to update and sumRange.Problem summary: 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 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]).
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Array · Design · Segment Tree
["NumArray","sumRange","update","sumRange"] [[[1,3,5]],[0,2],[1,2],[0,2]]
range-sum-query-immutable)range-sum-query-2d-mutable)shifting-letters-ii)Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #307: Range Sum Query - Mutable
class BinaryIndexedTree {
private int n;
private int[] c;
public BinaryIndexedTree(int n) {
this.n = n;
c = new int[n + 1];
}
public void update(int x, int delta) {
while (x <= n) {
c[x] += delta;
x += x & -x;
}
}
public int query(int x) {
int s = 0;
while (x > 0) {
s += c[x];
x -= x & -x;
}
return s;
}
}
class NumArray {
private BinaryIndexedTree tree;
public NumArray(int[] nums) {
int n = nums.length;
tree = new BinaryIndexedTree(n);
for (int i = 0; i < n; ++i) {
tree.update(i + 1, nums[i]);
}
}
public void update(int index, int val) {
int prev = sumRange(index, index);
tree.update(index + 1, val - prev);
}
public int sumRange(int left, int right) {
return tree.query(right + 1) - tree.query(left);
}
}
/**
* 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);
*/
// Accepted solution for LeetCode #307: Range Sum Query - Mutable
type BinaryIndexedTree struct {
n int
c []int
}
func newBinaryIndexedTree(n int) *BinaryIndexedTree {
c := make([]int, n+1)
return &BinaryIndexedTree{n, c}
}
func (t *BinaryIndexedTree) update(x, delta int) {
for ; x <= t.n; x += x & -x {
t.c[x] += delta
}
}
func (t *BinaryIndexedTree) query(x int) (s int) {
for ; x > 0; x -= x & -x {
s += t.c[x]
}
return s
}
type NumArray struct {
tree *BinaryIndexedTree
}
func Constructor(nums []int) NumArray {
tree := newBinaryIndexedTree(len(nums))
for i, v := range nums {
tree.update(i+1, v)
}
return NumArray{tree}
}
func (t *NumArray) Update(index int, val int) {
prev := t.SumRange(index, index)
t.tree.update(index+1, val-prev)
}
func (t *NumArray) SumRange(left int, right int) int {
return t.tree.query(right+1) - t.tree.query(left)
}
/**
* Your NumArray object will be instantiated and called as such:
* obj := Constructor(nums);
* obj.Update(index,val);
* param_2 := obj.SumRange(left,right);
*/
# Accepted solution for LeetCode #307: Range Sum Query - Mutable
class BinaryIndexedTree:
__slots__ = ["n", "c"]
def __init__(self, n):
self.n = n
self.c = [0] * (n + 1)
def update(self, x: int, delta: int):
while x <= self.n:
self.c[x] += delta
x += x & -x
def query(self, x: int) -> int:
s = 0
while x > 0:
s += self.c[x]
x -= x & -x
return s
class NumArray:
__slots__ = ["tree"]
def __init__(self, nums: List[int]):
self.tree = BinaryIndexedTree(len(nums))
for i, v in enumerate(nums, 1):
self.tree.update(i, v)
def update(self, index: int, val: int) -> None:
prev = self.sumRange(index, index)
self.tree.update(index + 1, val - prev)
def sumRange(self, left: int, right: int) -> int:
return self.tree.query(right + 1) - self.tree.query(left)
# Your NumArray object will be instantiated and called as such:
# obj = NumArray(nums)
# obj.update(index,val)
# param_2 = obj.sumRange(left,right)
// Accepted solution for LeetCode #307: Range Sum Query - Mutable
struct BitTree {
tree: Vec<i32>,
data: Vec<i32>,
n: usize,
}
impl BitTree {
fn new(n: usize) -> Self {
let tree = vec![0; n];
let data = vec![0; n];
let n = n;
BitTree { tree, data, n }
}
fn get(&self, i: usize) -> i32 {
self.data[i]
}
fn sum(&self, i: usize) -> i32 {
let mut res = 0;
let down_iter = std::iter::successors(Some(i), |&i| {
let j = i & (i + 1);
if j > 0 {
Some(j - 1)
} else {
None
}
});
for j in down_iter {
res += self.tree[j];
}
res
}
fn add(&mut self, i: usize, v: i32) {
self.data[i] += v;
let n = self.n;
let up_iter = std::iter::successors(Some(i), |&i| {
let j = i | (i + 1);
if j < n {
Some(j)
} else {
None
}
});
for j in up_iter {
self.tree[j] += v;
}
}
}
struct NumArray {
bit_tree: BitTree,
}
impl NumArray {
fn new(nums: Vec<i32>) -> Self {
let n = nums.len();
let mut bit_tree = BitTree::new(n);
for i in 0..n {
bit_tree.add(i, nums[i]);
}
NumArray { bit_tree }
}
fn update(&mut self, i: i32, val: i32) {
let i = i as usize;
self.bit_tree.add(i as usize, val - self.bit_tree.get(i))
}
fn sum_range(&self, i: i32, j: i32) -> i32 {
let i = i as usize;
let j = j as usize;
if i > 0 {
self.bit_tree.sum(j) - self.bit_tree.sum(i - 1)
} else {
self.bit_tree.sum(j)
}
}
}
#[test]
fn test() {
let nums = vec![1, 3, 5];
let mut obj = NumArray::new(nums);
assert_eq!(obj.sum_range(0, 2), 9);
obj.update(1, 2);
assert_eq!(obj.sum_range(0, 2), 8);
}
// Accepted solution for LeetCode #307: Range Sum Query - Mutable
class BinaryIndexedTree {
private n: number;
private c: number[];
constructor(n: number) {
this.n = n;
this.c = Array(n + 1).fill(0);
}
update(x: number, delta: number): void {
while (x <= this.n) {
this.c[x] += delta;
x += x & -x;
}
}
query(x: number): number {
let s = 0;
while (x > 0) {
s += this.c[x];
x -= x & -x;
}
return s;
}
}
class NumArray {
private tree: BinaryIndexedTree;
constructor(nums: number[]) {
const n = nums.length;
this.tree = new BinaryIndexedTree(n);
for (let i = 0; i < n; ++i) {
this.tree.update(i + 1, nums[i]);
}
}
update(index: number, val: number): void {
const prev = this.sumRange(index, index);
this.tree.update(index + 1, val - prev);
}
sumRange(left: number, right: number): number {
return this.tree.query(right + 1) - this.tree.query(left);
}
}
/**
* Your NumArray object will be instantiated and called as such:
* var obj = new NumArray(nums)
* obj.update(index,val)
* var param_2 = obj.sumRange(left,right)
*/
Use this to step through a reusable interview workflow for this problem.
Use a simple list or array for storage. Each operation (get, put, remove) requires a linear scan to find the target element — O(n) per operation. Space is O(n) to store the data. The linear search makes this impractical for frequent operations.
Design problems target O(1) amortized per operation by combining data structures (hash map + doubly-linked list for LRU, stack + min-tracking for MinStack). Space is always at least O(n) to store the data. The challenge is achieving constant-time operations through clever structure composition.
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.