Optimize the code · Medium

Speed Up Range Sum Queries

Medium Make it fast Seen at Google, Bloomberg

maxRangeSum(nums, lefts, rights) answers many range queries. Query i covers the indices between lefts[i] and rights[i] inclusive (in either order). Return the largest of the query sums.

The version below adds up every range from scratch. With 80,000 numbers and 80,000 queries it is far too slow. Make it fast without changing the results.

Example 1

Input: nums = [3, -1, 4, 1], lefts = [0, 3], rights = [2, 1]
Output: 6
Explanation: Query 0 sums indices 0..2 = 6; query 1 sums indices 1..3 = 4.

Constraints

  • 1 <= lefts.length == rights.length
  • 0 <= lefts[i], rights[i] < nums.length

The slow code

Every case must finish within 300 ms (budgets are per language in the editor).

function maxRangeSum(nums, lefts, rights) {
  let best = -Infinity;
  for (let q = 0; q < lefts.length; q++) {
    const lo = Math.min(lefts[q], rights[q]);
    const hi = Math.max(lefts[q], rights[q]);
    let sum = 0;
    for (let i = lo; i <= hi; i++) sum += nums[i];
    best = Math.max(best, sum);
  }
  return best;
}

Hints

Hint 1

What is the sum of nums[l..r] in terms of the sums of two prefixes?

Hint 2

Build prefix[i] = nums[0] + ... + nums[i - 1] once.

Make it faster

Loading the editor… (JavaScript is required to run code in your browser.)

Solution and explanation

Show the solution

Time O(n + q) · Space O(n)

Why it is slow: each query loops over its range, so the cost is O(q x average range length), here around 2 billion additions.

The fix: precompute prefix sums once: prefix[0] = 0, prefix[i + 1] = prefix[i] + nums[i]. Then the sum of nums[l..r] is prefix[r + 1] - prefix[l], O(1) per query. Total work O(n + q).

Prefix sums are the go-to answer whenever the data is static and the same kind of range aggregate is asked many times. When values change between queries, the follow-up answer is a Fenwick (binary indexed) tree or a segment tree.

Optimized solution

function maxRangeSum(nums, lefts, rights) {
  const prefix = new Array(nums.length + 1);
  prefix[0] = 0;
  for (let i = 0; i < nums.length; i++) prefix[i + 1] = prefix[i] + nums[i];
  let best = -Infinity;
  for (let q = 0; q < lefts.length; q++) {
    const lo = Math.min(lefts[q], rights[q]);
    const hi = Math.max(lefts[q], rights[q]);
    best = Math.max(best, prefix[hi + 1] - prefix[lo]);
  }
  return best;
}

Common mistakes

  • Off-by-one in the prefix indices: prefix has n + 1 entries.
  • Forgetting that a query may give its ends in reverse order.

Follow-up questions

  • Values are updated between queries (Fenwick tree).
  • 2-D version: sums over sub-rectangles of a matrix.