370. Range Addition 🔒
Problem Description
You start with an array of zeros with a given length
. You're given a series of updates
where each update is a triplet [startIdx, endIdx, inc]
that tells you to:
- Add
inc
to every element from indexstartIdx
toendIdx
(inclusive)
Your task is to apply all these updates to the array and return the final result.
Example walkthrough:
- If
length = 5
andupdates = [[1, 3, 2], [2, 4, 3]]
- Start with:
[0, 0, 0, 0, 0]
- After first update (add 2 to indices 1-3):
[0, 2, 2, 2, 0]
- After second update (add 3 to indices 2-4):
[0, 2, 5, 5, 3]
- Return:
[0, 2, 5, 5, 3]
The challenge is to find an efficient way to handle potentially many updates without actually iterating through each range for every update, which would be inefficient. The solution uses a difference array technique:
-
Instead of updating ranges directly, mark the boundaries:
- When adding
c
to range[l, r]
, markd[l] += c
(start of increase) - Mark
d[r+1] -= c
(end of increase) ifr+1
is within bounds
- When adding
-
After marking all boundaries, compute the prefix sum of this difference array to get the final result. The prefix sum naturally propagates the increments across the ranges.
This approach reduces the time complexity from O(n × m) for naive range updates to O(n + m) where n is the array length and m is the number of updates.
Intuition
Think about what happens when you add a value to a range. If you were to track the changes between consecutive elements rather than the elements themselves, adding a constant to a range creates a very specific pattern:
- The difference increases by
c
at the start of the range - The difference decreases by
c
right after the range ends - All differences within the range remain unchanged
For example, if we add 3 to indices [1, 3] in array [0, 0, 0, 0, 0]
:
- Original:
[0, 0, 0, 0, 0]
- After update:
[0, 3, 3, 3, 0]
- Differences between consecutive elements:
[0, +3, 0, 0, -3]
Notice how we only needed to mark two positions: where the increment starts (+3
at index 1) and where it ends (-3
at index 4).
The key insight is that instead of laboriously updating every element in each range, we can just mark these "boundary events" in a difference array. When we later compute the prefix sum of this difference array, the increments naturally "flow" across their ranges:
- The
+c
at the start propagates through the prefix sum - The
-c
after the end cancels it out - Multiple overlapping ranges automatically combine through addition
This transforms the problem from "update many elements many times" to "mark boundaries, then reconstruct once" - a much more efficient approach. The prefix sum acts like a running total that accumulates all the boundary changes we've marked, giving us the final array values.
Solution Approach
The implementation uses a difference array technique to efficiently handle range updates:
Step 1: Initialize the difference array
d = [0] * length
Create a difference array d
of the same size as our target array, initialized with zeros.
Step 2: Mark boundaries for each update
for l, r, c in updates: d[l] += c if r + 1 < length: d[r + 1] -= c
For each update [l, r, c]
:
- Add
c
tod[l]
- this marks where the increment starts - Subtract
c
fromd[r + 1]
(if within bounds) - this marks where the increment ends
The boundary check if r + 1 < length
prevents index out of bounds when the range extends to the last element.
Step 3: Compute prefix sum to get final array
return list(accumulate(d))
Use Python's accumulate
function to compute the running sum of the difference array. This reconstructs the final array by propagating all the marked changes.
Example walkthrough:
length = 5
,updates = [[1,3,2], [2,4,3]]
- Initial difference array:
[0, 0, 0, 0, 0]
- After first update
[1,3,2]
:d = [0, 2, 0, 0, -2]
- After second update
[2,4,3]
:d = [0, 2, 3, 0, -2]
(note:d[5]
would be-3
but it's out of bounds) - Prefix sum:
[0, 2, 5, 5, 3]
Time Complexity: O(n + m) where n is the array length and m is the number of updates Space Complexity: O(n) for the difference array
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's trace through a small example to see how the difference array technique works.
Given: length = 4
, updates = [[0,2,3], [1,3,2]]
Step 1: Initialize difference array
d = [0, 0, 0, 0]
Step 2: Process first update [0,2,3]
- Add 3 to range [0,2]
- Mark
d[0] += 3
(start of range) - Mark
d[3] -= 3
(end of range + 1)
d = [3, 0, 0, -3]
Step 3: Process second update [1,3,2]
- Add 2 to range [1,3]
- Mark
d[1] += 2
(start of range) d[4]
would be-= 2
, but index 4 is out of bounds, so skip
d = [3, 2, 0, -3]
Step 4: Compute prefix sum
- Position 0:
3
→ result[0] = 3 - Position 1:
3 + 2 = 5
→ result[1] = 5 - Position 2:
5 + 0 = 5
→ result[2] = 5 - Position 3:
5 + (-3) = 2
→ result[3] = 2
Final result: [3, 5, 5, 2]
Verification with naive approach:
- Start:
[0, 0, 0, 0]
- After [0,2,3]:
[3, 3, 3, 0]
- After [1,3,2]:
[3, 5, 5, 2]
✓
The difference array elegantly handles overlapping ranges - notice how position 1 and 2 automatically combine both updates through the prefix sum computation.
Solution Implementation
1from typing import List
2from itertools import accumulate
3
4class Solution:
5 def getModifiedArray(self, length: int, updates: List[List[int]]) -> List[int]:
6 # Initialize difference array with zeros
7 # This array will store the differences between consecutive elements
8 difference_array = [0] * length
9
10 # Process each update operation [start_index, end_index, increment_value]
11 for start_idx, end_idx, increment in updates:
12 # Add increment at the start position
13 # This marks the beginning of the range update
14 difference_array[start_idx] += increment
15
16 # Subtract increment at position after end_idx
17 # This marks the end of the range update
18 if end_idx + 1 < length:
19 difference_array[end_idx + 1] -= increment
20
21 # Apply prefix sum to get the final array
22 # The cumulative sum of differences gives us the actual values
23 return list(accumulate(difference_array))
24
1class Solution {
2 public int[] getModifiedArray(int length, int[][] updates) {
3 // Initialize difference array to track range updates
4 int[] differenceArray = new int[length];
5
6 // Process each update operation
7 for (int[] update : updates) {
8 int startIndex = update[0];
9 int endIndex = update[1];
10 int increment = update[2];
11
12 // Mark the start of the range with positive increment
13 differenceArray[startIndex] += increment;
14
15 // Mark the position after the end of range with negative increment
16 // This ensures the increment doesn't affect elements beyond the range
17 if (endIndex + 1 < length) {
18 differenceArray[endIndex + 1] -= increment;
19 }
20 }
21
22 // Apply prefix sum to get the final array values
23 // Each position accumulates all the increments that affect it
24 for (int i = 1; i < length; ++i) {
25 differenceArray[i] += differenceArray[i - 1];
26 }
27
28 return differenceArray;
29 }
30}
31
1class Solution {
2public:
3 vector<int> getModifiedArray(int length, vector<vector<int>>& updates) {
4 // Initialize difference array with all zeros
5 vector<int> differenceArray(length);
6
7 // Process each update operation
8 for (const auto& update : updates) {
9 int startIndex = update[0];
10 int endIndex = update[1];
11 int increment = update[2];
12
13 // Mark the start of the range with positive increment
14 differenceArray[startIndex] += increment;
15
16 // Mark the position after the end of range with negative increment
17 // to cancel out the effect for subsequent elements
18 if (endIndex + 1 < length) {
19 differenceArray[endIndex + 1] -= increment;
20 }
21 }
22
23 // Convert difference array to actual result array using prefix sum
24 // Each element becomes the cumulative sum up to that position
25 for (int i = 1; i < length; ++i) {
26 differenceArray[i] += differenceArray[i - 1];
27 }
28
29 return differenceArray;
30 }
31};
32
1/**
2 * Applies range update operations to an array and returns the final result.
3 * Uses difference array technique for efficient range updates.
4 *
5 * @param length - The length of the target array
6 * @param updates - Array of update operations, each containing [startIndex, endIndex, incrementValue]
7 * @returns The modified array after applying all updates
8 */
9function getModifiedArray(length: number, updates: number[][]): number[] {
10 // Initialize difference array with all zeros
11 const differenceArray: number[] = Array(length).fill(0);
12
13 // Apply range updates using difference array technique
14 // Mark the start and end+1 positions for each update
15 for (const [startIndex, endIndex, incrementValue] of updates) {
16 // Add increment value at the start of the range
17 differenceArray[startIndex] += incrementValue;
18
19 // Subtract increment value at position after the end of range
20 // This ensures the increment only applies within [startIndex, endIndex]
21 if (endIndex + 1 < length) {
22 differenceArray[endIndex + 1] -= incrementValue;
23 }
24 }
25
26 // Calculate prefix sum to get the final array values
27 // Each position accumulates all the differences up to that point
28 for (let index = 1; index < length; ++index) {
29 differenceArray[index] += differenceArray[index - 1];
30 }
31
32 return differenceArray;
33}
34
Time and Space Complexity
The time complexity is O(n + m)
, where n
is the length of the array and m
is the number of updates. The algorithm iterates through all m
updates to populate the difference array (taking O(m)
time), then performs a prefix sum operation using accumulate()
which takes O(n)
time to traverse the entire array.
The space complexity is O(n)
. The algorithm creates a difference array d
of size n
to store the incremental changes, and the final result array returned by accumulate()
also requires O(n)
space. No additional data structures that scale with input size are used.
Common Pitfalls
1. Index Out of Bounds When Marking End Boundary
The Pitfall:
The most common mistake is forgetting to check if end_idx + 1
is within the array bounds before updating difference_array[end_idx + 1]
. This leads to an IndexError when the update range extends to the last element of the array.
Incorrect Code:
for start_idx, end_idx, increment in updates: difference_array[start_idx] += increment difference_array[end_idx + 1] -= increment # ❌ IndexError when end_idx == length - 1
Correct Solution:
for start_idx, end_idx, increment in updates: difference_array[start_idx] += increment if end_idx + 1 < length: # ✅ Check bounds first difference_array[end_idx + 1] -= increment
2. Misunderstanding the Difference Array Concept
The Pitfall: Some developers try to directly update the range in each iteration, missing the entire point of the difference array optimization.
Incorrect Approach:
result = [0] * length
for start_idx, end_idx, increment in updates:
for i in range(start_idx, end_idx + 1): # ❌ O(n*m) complexity
result[i] += increment
return result
Why It's Wrong: This defeats the purpose of using a difference array and results in O(n × m) time complexity instead of O(n + m).
3. Off-by-One Error in Range Boundaries
The Pitfall: Confusing whether the end index is inclusive or exclusive, leading to incorrect boundary marking.
Common Mistake:
# Thinking end_idx is exclusive and marking wrong boundary difference_array[start_idx] += increment if end_idx < length: # ❌ Wrong condition difference_array[end_idx] -= increment # ❌ Wrong index
Remember: The problem states end_idx is inclusive, so we need to mark the boundary at end_idx + 1
to stop the increment effect after position end_idx
.
4. Not Returning the Accumulated Result
The Pitfall: Returning the difference array itself instead of its prefix sum.
Incorrect:
return difference_array # ❌ Returns differences, not final values
Correct:
return list(accumulate(difference_array)) # ✅ Returns cumulative sum
What's the relationship between a tree and a graph?
Recommended Readings
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Don’t Miss This!