Skip to main content

Prefix Sum

Efficient Range Updates Using Increment and Decrement Technique

Introduction

The increment and decrement technique is an efficient way to perform range updates on an array. Instead of updating every element in the range explicitly, we use a clever approach to mark the start and end of a range with increments and decrements. Once all queries are processed, a prefix sum is applied to derive the final state of the array.

This technique significantly improves performance, especially for large arrays and multiple range update queries. Below, we explain the technique in detail with examples and visualizations.


Problem Statement

Suppose you are given an array of size n initialized to all zeros and a list of range queries. Each query specifies a range [left, right] where you need to increment all elements in this range by 1.

For example:

  • Array size: n = 12
  • Range queries: [[1, 5], [2, 10], [4, 8]]

The goal is to determine the final state of the array after processing all range queries.


Technique Explanation

Step 1: Increment and Decrement Marking

For each range [left, right]:

  1. Increment the element at index left by 1.
  2. Decrement the element at index right + 1 by 1 (if right + 1 is within bounds).

This step marks where a range starts and ends in the array.

Step 2: Apply Prefix Sum

After processing all range queries, compute the prefix sum of the array. This accumulates the effect of all ranges, resulting in the final state of the array.


Example Walkthrough

Input:

  • Array size: n = 12
  • Range queries: [[1, 5], [2, 10], [4, 8]]

Step 1: Increment and Decrement Marking

We start with an array initialized to zeros:

Initial array: [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

Process each query:

  1. Query [1, 5]:
    • Increment at index 1: array[1] += 1
    • Decrement at index 6: array[6] -= 1
    • Resulting array: [0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0]
  2. Query [2, 10]:
    • Increment at index 2: array[2] += 1
    • Decrement at index 11: array[11] -= 1
    • Resulting array: [0, 1, 1, 0, 0, 0, -1, 0, 0, 0, 0, -1]
  3. Query [4, 8]:
    • Increment at index 4: array[4] += 1
    • Decrement at index 9: array[9] -= 1
    • Resulting array: [0, 1, 1, 0, 1, 0, -1, 0, 0, -1, 0, -1]

Step 2: Apply Prefix Sum

Compute the prefix sum of the array to accumulate the effect of the range updates:

Index 0 1 2 3 4 5 6 7 8 9 10 11
Array 0 1 1 0 1 0 -1 0 0 -1 0 -1

Compute the prefix sum:

Prefix sum: [0, 1, 2, 2, 3, 3, 2, 2, 2, 1, 1, 0]

Final Array:

The final state of the array after processing all range queries is:

[0, 1, 2, 2, 3, 3, 2, 2, 2, 1, 1, 0]

Visualization

Step 1: Marking Increments and Decrements

Initial array:
[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]

After processing [1, 5]:
[0, 1, 0, 0, 0, 0, -1, 0, 0, 0, 0, 0]

After processing [2, 10]:
[0, 1, 1, 0, 0, 0, -1, 0, 0, 0, 0, -1]

After processing [4, 8]:
[0, 1, 1, 0, 1, 0, -1, 0, 0, -1, 0, -1]

Step 2: Prefix Sum Calculation

Prefix sum:
[0, 1, 2, 2, 3, 3, 2, 2, 2, 1, 1, 0]

Advantages of This Technique

  1. Efficiency: Each range update is O(1) since we only mark the start and end points.
  2. Scalability: Applying the prefix sum is O(n), making this approach suitable for large arrays and numerous queries.
  3. Simplicity: The logic is straightforward and easy to implement.

Applications

  • Cumulative Range Updates: Efficiently handle multiple range updates in problems like lighting up ranges in a street, adding intervals in a timeline, etc.
  • Array Manipulations: This technique can also be extended for more complex operations like subtracting, multiplying, or assigning values within ranges.

Code Implementation

Java Code

import java.util.*;

public class Main {
    public static int[] rangeUpdate(int n, int[][] queries) {
        int[] prefixSum = new int[n];

        for (int[] query : queries) {
            int left = query[0];
            int right = query[1];
            prefixSum[left] += 1;
            prefixSum[right + 1] -= 1;
        }

        for (int i = 1; i < n; i++) {
            prefixSum[i] += prefixSum[i - 1];
        }

        return prefixSum;
    }

    public static void main(String[] args) {
        int n = 12;
        int[][] queries = {{1, 5}, {2, 10}, {4, 8}};
        int[] result = rangeUpdate(n, queries);

        System.out.println(Arrays.toString(result));
    }
}

C++ Code

#include <vector>
#include <iostream>
using namespace std;

class Solution {
public:
    vector<int> rangeUpdate(int n, vector<vector<int>>& queries) {
        vector<int> prefixSum(n, 0);

        for (auto &query : queries) {
            int left = query[0];
            int right = query[1];
            prefixSum[left] += 1;
            prefixSum[right + 1] -= 1;
        }

        for (int i = 1; i < n; i++) {
            prefixSum[i] += prefixSum[i - 1];
        }

        return prefixSum;
    }
};

int main() {
    int n = 12;
    vector<vector<int>> queries = {{1, 5}, {2, 10}, {4, 8}};
    Solution solution;
    vector<int> result = solution.rangeUpdate(n, queries);

    for (int val : result) {
        cout << val << " ";
    }
    return 0;
}

Python Code

class Solution:
    def range_update(self, n, queries):
        prefix_sum = [0] * n

        for left, right in queries:
            prefix_sum[left] += 1
            prefix_sum[right + 1] -= 1

        for i in range(1, n):
            prefix_sum[i] += prefix_sum[i - 1]

        return prefix_sum

n = 12
queries = [[1, 5], [2, 10], [4, 8]]
solution = Solution()
result = solution.range_update(n, queries)
print(result)

Complexity Analysis

Time Complexity

The time complexity of this approach is O(N + Q), where:

  • N is the size of the array.
  • Q is the number of ranges.

We traverse the query array once to apply increments and decrements and then we traverse the prefixSum array to compute the prefix sum.

Space Complexity

The space complexity is O(N) for the array used to store intermediate results.