LeetCode 2569 - Handling Sum Queries After Update
This problem gives us two arrays, nums1 and nums2, both of the same length n, along with a list of queries. Each query modifies one of the arrays or asks for information about them.
Difficulty: 🔴 Hard
Topics: Array, Segment Tree
Solution
LeetCode 2569 - Handling Sum Queries After Update
Problem Understanding
This problem gives us two arrays, nums1 and nums2, both of the same length n, along with a list of queries. Each query modifies one of the arrays or asks for information about them.
The important detail is that nums1 only contains binary values, meaning every element is either 0 or 1. The three query types behave differently:
- Type 1 flips all bits in a subarray of
nums1. Every0becomes1, and every1becomes0. - Type 2 updates every element of
nums2by addingnums1[i] * pto it. - Type 3 asks for the total sum of all values currently in
nums2.
The goal is to return the results for all type 3 queries.
A direct implementation may seem straightforward at first, but the constraints are extremely large:
- Array size can be up to
10^5 - Number of queries can also be up to
10^5
This means any approach that repeatedly scans entire arrays for updates will become too slow.
The key observation is that type 2 queries do not actually require modifying every element of nums2. Since:
nums2[i] += nums1[i] * p
the total increase to the sum of nums2 is simply:
(number of 1s in nums1) * p
Therefore, instead of storing all updates in nums2, we only need:
- The current sum of
nums2 - The current count of
1s innums1
The difficult part becomes efficiently supporting range bit flips while maintaining the number of 1s. That is exactly where a segment tree with lazy propagation becomes useful.
Important edge cases include:
- Flipping the same range multiple times
- Arrays of length
1 - Queries where
p = 0 - Entire array flips
- Large sequences of overlapping updates
The problem guarantees valid indices and properly formatted queries, so we do not need additional bounds checking.
Approaches
Brute Force Approach
A naive implementation would directly simulate every query.
For type 1 queries:
- Iterate from
ltor - Flip each bit individually
For type 2 queries:
- Iterate through all indices
- Update each
nums2[i]
For type 3 queries:
- Compute the sum of
nums2
This approach is correct because it exactly follows the problem statement. However, it is far too slow.
Suppose we have 10^5 queries and each query processes 10^5 elements. The total complexity becomes:
O(n * q) = O(10^10)
which is infeasible.
Optimal Approach
The optimization comes from recognizing that we never truly need the individual values inside nums2.
For type 2 queries:
nums2[i] += nums1[i] * p
the total increase to the sum is:
p * count_of_ones_in_nums1
So instead of updating the entire array, we maintain only:
total_sum = sum(nums2)ones_count = number of 1s in nums1
Now the main challenge becomes efficiently handling range flips in nums1.
A segment tree with lazy propagation solves this efficiently.
Each node stores:
- The number of
1s in its segment
When flipping a range:
- The number of
1s becomes:
segment_length - current_ones
Lazy propagation allows postponing updates until necessary, making each operation run in O(log n) time.
Approach Comparison
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(n * q) | O(1) | Direct simulation of every query |
| Optimal | O(q log n) | O(n) | Segment tree with lazy propagation |
Algorithm Walkthrough
- Initialize the total sum of
nums2.
Since type 3 queries only ask for the total sum, we do not need to preserve individual values in nums2. We compute:
total_sum = sum(nums2)
- Build a segment tree over
nums1.
Each segment tree node stores the number of 1s in its segment.
For example:
nums1 = [1,0,1,1]
The root node stores 3, because there are three 1s.
3. Add lazy propagation support.
Range flips are expensive if handled element by element.
Instead, each node has a lazy flag indicating whether its segment should be flipped later.
When a segment is flipped:
ones = segment_length - ones
and the lazy flag is toggled. 4. Process each query.
For type 1:
- Perform a range flip on the segment tree
- Update affected counts automatically
For type 2:
- Get the current number of
1s from the root node - Increase the total sum by:
ones_count * p
For type 3:
- Append the current total sum to the answer list
- Return all recorded answers.
Why it works
The core invariant is that the segment tree root always stores the current total number of 1s in nums1.
Because type 2 queries only depend on how many positions contain 1, we can update the global sum directly without modifying nums2 element by element.
Lazy propagation guarantees that all range flips are correctly represented while avoiding unnecessary repeated updates. Therefore every query produces the correct result efficiently.
Python Solution
from typing import List
class Solution:
def handleQuery(
self,
nums1: List[int],
nums2: List[int],
queries: List[List[int]]
) -> List[int]:
n = len(nums1)
tree = [0] * (4 * n)
lazy = [False] * (4 * n)
def build(node: int, left: int, right: int) -> None:
if left == right:
tree[node] = nums1[left]
return
mid = (left + right) // 2
build(node * 2, left, mid)
build(node * 2 + 1, mid + 1, right)
tree[node] = tree[node * 2] + tree[node * 2 + 1]
def apply_flip(node: int, left: int, right: int) -> None:
tree[node] = (right - left + 1) - tree[node]
lazy[node] = not lazy[node]
def push(node: int, left: int, right: int) -> None:
if not lazy[node] or left == right:
return
mid = (left + right) // 2
apply_flip(node * 2, left, mid)
apply_flip(node * 2 + 1, mid + 1, right)
lazy[node] = False
def update(
node: int,
left: int,
right: int,
query_left: int,
query_right: int
) -> None:
if query_left <= left and right <= query_right:
apply_flip(node, left, right)
return
push(node, left, right)
mid = (left + right) // 2
if query_left <= mid:
update(
node * 2,
left,
mid,
query_left,
query_right
)
if query_right > mid:
update(
node * 2 + 1,
mid + 1,
right,
query_left,
query_right
)
tree[node] = tree[node * 2] + tree[node * 2 + 1]
build(1, 0, n - 1)
total_sum = sum(nums2)
answers = []
for query_type, x, y in queries:
if query_type == 1:
update(1, 0, n - 1, x, y)
elif query_type == 2:
ones_count = tree[1]
total_sum += ones_count * x
else:
answers.append(total_sum)
return answers
The implementation starts by building a segment tree over nums1. Each node stores how many 1s exist in its segment.
The apply_flip function performs the key transformation:
tree[node] = segment_length - tree[node]
because flipping bits converts all 1s into 0s and vice versa.
The lazy array tracks deferred flips. When a node has a pending flip, the push function propagates that update to its children only when necessary.
The update function performs range flips in O(log n) time using lazy propagation.
During query processing:
- Type 1 triggers a segment tree range update
- Type 2 uses the root value
tree[1], which always stores the total number of1s - Type 3 records the current total sum
No explicit updates to nums2 are ever performed after initialization.
Go Solution
func handleQuery(nums1 []int, nums2 []int, queries [][]int) []int64 {
n := len(nums1)
tree := make([]int, 4*n)
lazy := make([]bool, 4*n)
var build func(node, left, right int)
build = func(node, left, right int) {
if left == right {
tree[node] = nums1[left]
return
}
mid := (left + right) / 2
build(node*2, left, mid)
build(node*2+1, mid+1, right)
tree[node] = tree[node*2] + tree[node*2+1]
}
applyFlip := func(node, left, right int) {
tree[node] = (right - left + 1) - tree[node]
lazy[node] = !lazy[node]
}
var push func(node, left, right int)
push = func(node, left, right int) {
if !lazy[node] || left == right {
return
}
mid := (left + right) / 2
applyFlip(node*2, left, mid)
applyFlip(node*2+1, mid+1, right)
lazy[node] = false
}
var update func(node, left, right, ql, qr int)
update = func(node, left, right, ql, qr int) {
if ql <= left && right <= qr {
applyFlip(node, left, right)
return
}
push(node, left, right)
mid := (left + right) / 2
if ql <= mid {
update(node*2, left, mid, ql, qr)
}
if qr > mid {
update(node*2+1, mid+1, right, ql, qr)
}
tree[node] = tree[node*2] + tree[node*2+1]
}
build(1, 0, n-1)
var totalSum int64
for _, value := range nums2 {
totalSum += int64(value)
}
answer := []int64{}
for _, query := range queries {
queryType := query[0]
if queryType == 1 {
update(1, 0, n-1, query[1], query[2])
} else if queryType == 2 {
onesCount := tree[1]
totalSum += int64(onesCount * query[1])
} else {
answer = append(answer, totalSum)
}
}
return answer
}
The Go implementation follows the same logic as the Python solution. One important difference is integer handling.
The sum of nums2 can become very large because:
nums2[i]can reach10^9- There can be
10^5elements - Additional updates may further increase the total
Therefore the solution uses int64 for the final sum and returned answers.
Go slices are dynamically sized, so the answer list is built using repeated append operations.
Worked Examples
Example 1
nums1 = [1,0,1]
nums2 = [0,0,0]
queries = [[1,1,1],[2,1,0],[3,0,0]]
Initial state:
| Variable | Value |
|---|---|
| nums1 | [1,0,1] |
| total_sum | 0 |
| ones_count | 2 |
Query 1: [1,1,1]
Flip indices 1 to 1.
nums1 becomes [1,1,1]
Updated state:
| Variable | Value |
|---|---|
| ones_count | 3 |
| total_sum | 0 |
Query 2: [2,1,0]
Add:
ones_count * p = 3 * 1 = 3
Updated state:
| Variable | Value |
|---|---|
| total_sum | 3 |
Conceptually:
nums2 becomes [1,1,1]
Query 3: [3,0,0]
Output current sum:
3
Final answer:
[3]
Example 2
nums1 = [1]
nums2 = [5]
queries = [[2,0,0],[3,0,0]]
Initial state:
| Variable | Value |
|---|---|
| ones_count | 1 |
| total_sum | 5 |
Query 1: [2,0,0]
Increase:
1 * 0 = 0
No change.
| Variable | Value |
|---|---|
| total_sum | 5 |
Query 2: [3,0,0]
Output:
5
Final answer:
[5]
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(q log n) | Each range flip runs in logarithmic time |
| Space | O(n) | Segment tree and lazy arrays require linear space |
The segment tree contains approximately 4n nodes, which is standard for recursive segment tree implementations.
Each type 1 query performs a range update using lazy propagation, requiring O(log n) time.
Type 2 and type 3 queries are both O(1) because the root node always stores the current number of 1s.
Test Cases
sol = Solution()
# Example 1
assert sol.handleQuery(
[1, 0, 1],
[0, 0, 0],
[[1, 1, 1], [2, 1, 0], [3, 0, 0]]
) == [3] # basic flip and sum update
# Example 2
assert sol.handleQuery(
[1],
[5],
[[2, 0, 0], [3, 0, 0]]
) == [5] # multiplying by zero
# Single element flip
assert sol.handleQuery(
[0],
[10],
[[1, 0, 0], [2, 5, 0], [3, 0, 0]]
) == [15] # flip 0 to 1 then add
# Multiple overlapping flips
assert sol.handleQuery(
[1, 1, 0, 0],
[0, 0, 0, 0],
[
[1, 1, 3],
[1, 0, 2],
[2, 2, 0],
[3, 0, 0]
]
) == [4] # validates lazy propagation correctness
# Entire array flip
assert sol.handleQuery(
[1, 0, 1, 0],
[1, 1, 1, 1],
[
[1, 0, 3],
[2, 3, 0],
[3, 0, 0]
]
) == [10] # full range update
# Multiple type 3 queries
assert sol.handleQuery(
[1, 1],
[2, 2],
[
[3, 0, 0],
[2, 1, 0],
[3, 0, 0]
]
) == [4, 6] # repeated reporting
# No flips at all
assert sol.handleQuery(
[0, 0, 0],
[1, 2, 3],
[
[2, 10, 0],
[3, 0, 0]
]
) == [6] # zero ones means no increase
# Repeated flips cancel out
assert sol.handleQuery(
[1, 0],
[0, 0],
[
[1, 0, 1],
[1, 0, 1],
[2, 4, 0],
[3, 0, 0]
]
) == [4] # double flip restores original state
| Test | Why |
|---|---|
| Basic example | Verifies standard query flow |
| Multiply by zero | Ensures no accidental updates |
| Single element array | Smallest valid input |
| Overlapping flips | Tests lazy propagation correctness |
| Entire array flip | Validates root segment updates |
| Multiple outputs | Ensures answers accumulate correctly |
| No ones in nums1 | Confirms type 2 can add zero |
| Repeated flips | Ensures toggles cancel properly |
Edge Cases
Single Element Arrays
When n = 1, the segment tree contains only one meaningful node. Recursive implementations sometimes fail on minimal ranges due to incorrect midpoint handling or unnecessary child propagation.
This implementation safely handles that case because the recursion stops immediately when:
left == right
and lazy propagation does not attempt to push updates further.
Repeated Flips on the Same Range
Flipping the same range twice should restore the original values. This can easily introduce bugs if lazy flags are overwritten instead of toggled.
The implementation correctly handles this with:
lazy[node] = not lazy[node]
which preserves the parity of flips.
Large Sum Values
The total sum of nums2 can become extremely large after many updates. Using standard 32 bit integers may overflow.
The Go solution explicitly uses int64, and Python naturally supports arbitrarily large integers. This guarantees correctness even for maximum constraints.