➕ Prefix Sum — JavaScript

Index: Prefix Sum Index | Parent: JS Array Index


Core Concept

Build a prefix array where prefix[i] = sum of all elements from index 0 to i-1.

function buildPrefix(arr) {
  const prefix = new Array(arr.length + 1).fill(0);
  for (let i = 0; i < arr.length; i++) {
    prefix[i + 1] = prefix[i] + arr[i];
  }
  return prefix;
}

// Range sum [L, R] inclusive:
// sum = prefix[R + 1] - prefix[L]

Example:

arr:    [3,  1,  2,  5,  4]
idx:     0   1   2   3   4

prefix: [0,  3,  4,  6,  11, 15]
idx:     0   1   2   3   4    5

Sum [1..3] = prefix[4] - prefix[1] = 11 - 3 = 8  ✓ (1+2+5=8)

Q1. Range Sum Query — Class-Based [E]


🧠 How to Approach This

Step 1 — Recognize the pattern:

Multiple range sum queries on the same array = Pre-compute a prefix sum array once, answer each query in O(1).

Step 2 — Brute force:

Sum elements from left to right for each query → O(n) per query. With 10,000 queries that's O(n×q).

Step 3 — Optimal insight:

prefix[i] = sum of all elements from index 0 to i-1. Sum of range [L..R] = prefix[R+1] - prefix[L]. Pre-build once in O(n), query in O(1).

Step 4 — Algorithm:

1. prefix[0] = 0; prefix[i+1] = prefix[i] + nums[i]

2. sumRange(L, R) = prefix[R+1] - prefix[L]

Step 5 — Edge cases:

- L=0: prefix[0]=0, so prefix[R+1] - 0 = prefix[R+1] ✓

Way of Thinking: Build prefix once in constructor. Each query is O(1).

class NumArray {
  constructor(nums) {
    this.prefix = new Array(nums.length + 1).fill(0);
    for (let i = 0; i < nums.length; i++) {
      this.prefix[i + 1] = this.prefix[i] + nums[i];
    }
  }

  sumRange(left, right) {
    return this.prefix[right + 1] - this.prefix[left];
  }
}

const obj = new NumArray([-2, 0, 3, -5, 2, -1]);
console.log(obj.sumRange(0, 2)); // 1  (-2+0+3)
console.log(obj.sumRange(2, 5)); // -1 (3-5+2-1)

Q2. Subarray Sum Equals K [M][FB]


🧠 How to Approach This

Step 1 — Recognize the pattern:

Count subarrays with exact sum = prefix sum + hashmap complement trick.

Step 2 — Brute force:

All O(n²) subarrays, compute sum → O(n³) or O(n²). Need O(n).

Step 3 — Key insight:

Sum of subarray [j..i] = prefixSum[i] - prefixSum[j-1]. If this equals k, then prefixSum[j-1] = prefixSum[i] - k. So for each i, check if (currentSum - k) was seen before in the map.

Step 4 — Algorithm:

1. map = {0: 1} (prefix sum 0 seen once before we start)

2. For each num: sum += num

3. count += map.get(sum - k) ?? 0

4. map.set(sum, (map.get(sum) ?? 0) + 1)

Step 5 — Edge cases:

- k=0: counts subarrays that cancel out to zero (negative + positive)

- Negative numbers are handled naturally

Way of Thinking: Use a Map to count prefix sum occurrences. If prefixSum - k exists in the map, we found a valid subarray.

function subarraySum(nums, k) {
  const map = new Map([[0, 1]]); // prefix 0 seen once
  let sum = 0, count = 0;

  for (const num of nums) {
    sum += num;
    if (map.has(sum - k)) count += map.get(sum - k);
    map.set(sum, (map.get(sum) || 0) + 1);
  }
  return count;
}

console.log(subarraySum([1,1,1], 2)); // 2
console.log(subarraySum([1,2,3], 3)); // 2  ([1,2] and [3])

Q3. Equilibrium Index [M]


🧠 How to Approach This

Step 1 — Recognize the pattern:

Find index where left sum equals right sum = running prefix sum, no extra array needed.

Step 2 — Brute force:

For each i, sum left and sum right → O(n²). Need O(n).

Step 3 — Key insight:

rightSum = total - leftSum - arr[i]. Track leftSum as you walk. At each i: if leftSum === total - leftSum - arr[i] then i is equilibrium.

Step 4 — Algorithm:

1. total = sum of all elements

2. leftSum = 0

3. For each i: if leftSum === total - leftSum - arr[i]: return i; leftSum += arr[i]

Step 5 — Edge cases:

- Single element: always equilibrium (both sides are 0)

- No equilibrium: return -1

Way of Thinking: At equilibrium index i: leftSum = totalSum - leftSum - arr[i]. No need for prefix array — track running sum.

function equilibriumIndex(arr) {
  const total = arr.reduce((a, b) => a + b, 0);
  let leftSum = 0;

  for (let i = 0; i < arr.length; i++) {
    if (leftSum === total - leftSum - arr[i]) return i;
    leftSum += arr[i];
  }
  return -1;
}

console.log(equilibriumIndex([1, 3, 5, 2, 2])); // 2 (sum left=4, sum right=4)

Q4. Product of Array Except Self [M][FB]


🧠 How to Approach This

Step 1 — Recognize the pattern:

Product of everything except self, no division, O(n) = Two-pass prefix/suffix product.

Step 2 — Brute force:

For each i, multiply all other elements → O(n²). Or total product / arr[i] but fails on zeros.

Step 3 — Key insight:

result[i] = (product of everything to the LEFT of i) × (product of everything to the RIGHT of i). Left products in one forward pass, right products in one backward pass — no extra array needed for the second pass.

Step 4 — Algorithm:

1. Forward pass: result[i] = product of all nums[0..i-1] (start with left=1)

2. Backward pass: multiply result[i] by right product (start with right=1)

Step 5 — Edge cases:

- Array with one zero: all products are 0 except at the zero index

- Array with two zeros: all products are 0

Way of Thinking: Two passes: left pass builds prefix products, right pass builds suffix products. Multiply both. No division needed.

function productExceptSelf(nums) {
  const n = nums.length;
  const result = new Array(n).fill(1);

  // Left pass: result[i] = product of all elements left of i
  let left = 1;
  for (let i = 0; i < n; i++) {
    result[i] = left;
    left *= nums[i];
  }

  // Right pass: multiply by product of all elements right of i
  let right = 1;
  for (let i = n - 1; i >= 0; i--) {
    result[i] *= right;
    right *= nums[i];
  }

  return result;
}

console.log(productExceptSelf([1,2,3,4])); // [24,12,8,6]

Q5. Maximum Subarray Sum — Kadane's Algorithm [M][FB]


🧠 How to Approach This

Step 1 — Recognize the pattern:

Maximum sum of a contiguous subarray = Kadane's Algorithm (dynamic programming in O(n)).

Step 2 — Brute force:

All subarrays, compute sum → O(n²) or O(n³). Need O(n).

Step 3 — Key decision at each step:

Should we extend the current subarray or start a new one? If currentSum + nums[i] > nums[i] alone, extend. Otherwise restart from nums[i]. Simplified: currentSum = max(nums[i], currentSum + nums[i]).

Step 4 — Algorithm:

1. currentSum = maxSum = nums[0]

2. For i from 1 to end: currentSum = max(nums[i], currentSum + nums[i])

3. maxSum = max(maxSum, currentSum)

Step 5 — Edge cases:

- All negative: return the least negative number

- Single element: that is the answer

Way of Thinking: At each position: should we extend current subarray or start fresh? Take the max of (currentElement alone) vs (currentElement + previousSum).

function maxSubArray(nums) {
  let currentSum = nums[0];
  let maxSum = nums[0];

  for (let i = 1; i < nums.length; i++) {
    currentSum = Math.max(nums[i], currentSum + nums[i]);
    maxSum = Math.max(maxSum, currentSum);
  }
  return maxSum;
}

// Extension: return the subarray itself
function maxSubArrayWithIndices(nums) {
  let currentSum = nums[0], maxSum = nums[0];
  let start = 0, end = 0, tempStart = 0;

  for (let i = 1; i < nums.length; i++) {
    if (nums[i] > currentSum + nums[i]) {
      currentSum = nums[i];
      tempStart = i;
    } else {
      currentSum += nums[i];
    }
    if (currentSum > maxSum) {
      maxSum = currentSum;
      start = tempStart;
      end = i;
    }
  }
  return { maxSum, subarray: nums.slice(start, end + 1) };
}

console.log(maxSubArray([-2,1,-3,4,-1,2,1,-5,4])); // 6
console.log(maxSubArrayWithIndices([-2,1,-3,4,-1,2,1,-5,4]));
// { maxSum: 6, subarray: [4,-1,2,1] }

Q6. 2D Prefix Sum — Matrix Range Query [M]

Way of Thinking: Build 2D prefix: prefix[r][c] = sum of rectangle from (0,0) to (r-1, c-1). Use inclusion-exclusion for queries.

class NumMatrix {
  constructor(matrix) {
    const rows = matrix.length, cols = matrix[0].length;
    this.prefix = Array.from({length: rows+1}, () => new Array(cols+1).fill(0));

    for (let r = 1; r <= rows; r++) {
      for (let c = 1; c <= cols; c++) {
        this.prefix[r][c] =
          matrix[r-1][c-1] +
          this.prefix[r-1][c] +
          this.prefix[r][c-1] -
          this.prefix[r-1][c-1];
      }
    }
  }

  sumRegion(r1, c1, r2, c2) {
    return this.prefix[r2+1][c2+1]
         - this.prefix[r1][c2+1]
         - this.prefix[r2+1][c1]
         + this.prefix[r1][c1];
  }
}

const nm = new NumMatrix([[3,0,1,4],[5,6,3,2],[1,2,0,1]]);
console.log(nm.sumRegion(0,0,1,1)); // 14  (3+0+5+6)