[Hard] 327. Count of Range Sum
[Hard] 327. Count of Range Sum
Problem Statement
Given an integer array nums and two integers lower and upper, return the number of range sums that lie in [lower, upper] inclusive.
Range sum S(i, j) is defined as the sum of the elements in nums between indices i and j inclusive, where i <= j.
Examples
Example 1:
Input: nums = [-2,5,-1], lower = -2, upper = 2
Output: 3
Explanation: The three ranges are: [0,0], [2,2], and [0,2] and their respective sums are: -2, -1, 2.
Example 2:
Input: nums = [0], lower = 0, upper = 0
Output: 1
Constraints
1 <= nums.length <= 10^5-2^31 <= nums[i] <= 2^31 - 1-10^5 <= lower <= upper <= 10^5
Clarification Questions
Before diving into the solution, here are 5 important clarifications and assumptions to discuss during an interview:
-
Range inclusivity: Is the range
[lower, upper]inclusive on both ends? (Assumption: Yes - subarray sum should satisfylower <= sum <= upper) -
Subarray definition: Does a subarray need to be contiguous? (Assumption: Yes - subarray is contiguous by definition)
-
Empty subarray: Can an empty subarray be considered? (Assumption: Need to clarify - typically empty subarray has sum 0, but should confirm if it counts)
-
Integer overflow: Can prefix sums overflow? (Assumption: Yes - need to use
long longfor prefix sums to handle large numbers) -
Negative numbers: Can the array contain negative numbers? (Assumption: Yes - this makes prefix sums non-monotonic, requiring advanced techniques like merge sort or segment tree)
Interview Deduction Process (30 minutes)
Step 1: Brute-Force Approach (8 minutes)
Initial Thought: “I need to count range sums. Let me check all possible subarrays.”
Naive Solution: Check all possible subarrays, compute sum, count those in range [lower, upper].
Complexity: O(n²) time, O(1) space
Issues:
- O(n²) time - inefficient
- Repeats sum computation for overlapping subarrays
- Doesn’t leverage prefix sum
- Can be optimized
Step 2: Semi-Optimized Approach (10 minutes)
Insight: “I can use prefix sum to compute subarray sums efficiently, but need to count pairs.”
Improved Solution: Build prefix sum array. For each j, count i < j where prefix[j] - prefix[i] is in [lower, upper]. This requires counting prefix[i] in range [prefix[j]-upper, prefix[j]-lower].
Complexity: O(n²) time with prefix sum, O(n) space
Improvements:
- Prefix sum enables O(1) subarray sum queries
- Still O(n²) for checking all pairs
- Better structure than brute-force
- Can optimize further
Step 3: Optimized Solution (12 minutes)
Final Optimization: “I can use merge sort or segment tree to count pairs efficiently.”
Best Solution: Use merge sort on prefix sums. During merge, count pairs where prefix[i] is in range [prefix[j]-upper, prefix[j]-lower]. Alternative: Segment Tree with coordinate compression.
Complexity: O(n log n) time, O(n) space
Key Realizations:
- Prefix sum transformation is key insight
- Merge sort enables efficient pair counting
- O(n log n) time is optimal
- Segment Tree is alternative approach
Solution Approach
This problem requires counting the number of subarray sums that fall within a given range. The key insight is to use prefix sums and then count pairs of prefix sums that satisfy the condition.
Key Insights:
- Prefix Sum:
S(i, j) = prefix[j+1] - prefix[i] - Range Condition:
lower <= prefix[j+1] - prefix[i] <= upper - Rearranged:
prefix[j+1] - upper <= prefix[i] <= prefix[j+1] - lower - Problem Transformation: Count pairs
(i, j)wherei < jandprefix[i]is in range[prefix[j] - upper, prefix[j] - lower]
Solution 1: Divide and Conquer (Merge Sort)
class Solution:
def countRangeSum(self, nums, lower, upper):
n = len(nums)
prefix = [0] * (n + 1)
# prefix sum with 0 included
for i in range(0, n):
prefix[i + 1] = prefix[i] + nums[i]
temp = [0] * (n + 1)
return self.divide(prefix, 0, n, lower, upper, temp)
def divide(self, prefix, left, right, lower, upper, temp):
if left >= right:
return 0
mid = (left + right) // 2
count = 0
count += self.divide(prefix, left, mid, lower, upper, temp)
count += self.divide(prefix, mid + 1, right, lower, upper, temp)
count += self.countCross(prefix, left, mid, right, lower, upper)
self.merge(prefix, left, mid, right, temp)
return count
def countCross(self, prefix, left, mid, right, lower, upper):
count = 0
wl, wr = left, left
for i in range(mid + 1, right + 1):
low = prefix[i] - upper
high = prefix[i] - lower
while wl <= mid and prefix[wl] < low:
wl += 1
while wr <= mid and prefix[wr] <= high:
wr += 1
count += wr - wl
return count
def merge(self, prefix, left, mid, right, temp):
i, j, k = left, mid + 1, left
while i <= mid and j <= right:
if prefix[i] <= prefix[j]:
temp[k] = prefix[i]
i += 1
else:
temp[k] = prefix[j]
j += 1
k += 1
while i <= mid:
temp[k] = prefix[i]
i += 1
k += 1
while j <= right:
temp[k] = prefix[j]
j += 1
k += 1
for i in range(left, right + 1):
prefix[i] = temp[i]
Algorithm Explanation:
-
Prefix Sum Array: Build
prefix[i] = sum(nums[0..i-1])withprefix[0] = 0 - Divide and Conquer:
- Divide prefix array into left and right halves
- Recursively count valid pairs in left and right halves
- Count cross pairs (one from left, one from right)
- Merge the sorted halves
- Count Cross Pairs (
countCross):- For each
prefix[j]in right half, find validprefix[i]in left half - Condition:
prefix[j] - upper <= prefix[i] <= prefix[j] - lower - Use two pointers
wlandwrto maintain valid range wl: first index whereprefix[wl] >= prefix[j] - upperwr: first index whereprefix[wr] > prefix[j] - lower- Count:
wr - wl
- For each
- Merge: Standard merge sort merge operation
Why This Works?
- Sorted Property: After merge, left and right halves are sorted
- Two Pointers: For each right element, find valid left elements using sliding window
- Efficiency: O(n log n) time complexity
Complexity Analysis:
- Time Complexity: O(n log n)
- Divide: O(log n) levels
- Each level: O(n) for counting and merging
- Total: O(n log n)
- Space Complexity: O(n)
- Prefix array: O(n)
- Temp array: O(n)
- Recursion stack: O(log n)
Solution 2: Segment Tree (Dynamic Node Creation)
struct Node :
long long start, end
cnt
Node left, right
Node(long long start, long long end): start(start), end(end), cnt(0):
left = right = None
class Solution:
/
Prefix sum pre
pre[j] - upper <= pre[i] <= pre[j] - lower
Fenwick Tree (Binary Indexed Tree)
Segment Tree with dynamic allocation - reduce allocations, no delete needed
/
def countRangeSum(self, nums, lower, upper):
N = len(nums)
list[long long> presum(N + 1, 0)
for(i = 0 i < N i += 1) :
presum[i + 1] = presum[i] + nums[i]
long long mn = LLONG_MAX, mx = LLONG_MIN
for x in presum:
mn = min(:mn, x, x - upper)
mx = max(:mx, x, x - lower)
def root(self, mn, mx)
rtn = 0
for x in presum:
rtn += query(root, x - upper, x - lower)
add(root, x)
return rtn
def query(self, node, l, r):
if(not node) return 0
if(l > node.end or r < node.start) return 0
if(l <= node.start and node.end <= r) return node.cnt
return query(node.left, l, r) + query(node.right, l, r)
# node is not null
# add pos [start, end]
def add(self, node, pos):
node.cnt += 1
if(node.start == node.end) return
long long mid = (node.start + node.end) >> 1
if pos <= mid:
if not node.left:
node.left = Node(node.start, mid)
add(node.left, pos)
else :
if not node.right:
node.right = Node(mid+1, node.end)
add(node.right, pos)
Algorithm Explanation:
-
Prefix Sum Array: Build prefix sums as in Solution 1
- Dynamic Segment Tree:
- Root covers range
[min_value, max_value]of all possible prefix sums - Dynamically create nodes only when needed (lazy initialization)
- Each node stores count of prefix sums in its range
- Root covers range
- Query and Update:
- For each prefix sum
x, query count of values in[x - upper, x - lower] - This counts how many previous prefix sums satisfy the condition
- Then add current prefix sum to the tree
- For each prefix sum
- Dynamic Node Creation:
- Only create child nodes when needed (when adding a value)
- Reduces memory usage compared to full segment tree
Why This Works?
- Range Query: Segment tree efficiently counts values in range
- Online Processing: Process prefix sums one by one
- Memory Efficient: Only create nodes for values that appear
Complexity Analysis:
- Time Complexity: O(n log U)
- n = number of elements
- U = range size (max - min)
- Each query/update: O(log U)
- Space Complexity: O(n log U)
- Segment tree nodes: O(n log U) in worst case
- Prefix array: O(n)
Key Insights
- Prefix Sum Transformation: Convert subarray sum problem to prefix sum difference problem
- Range Condition:
lower <= prefix[j] - prefix[i] <= upperbecomesprefix[j] - upper <= prefix[i] <= prefix[j] - lower - Two Approaches:
- Divide & Conquer: Sort prefix sums, count pairs using two pointers
- Segment Tree: Maintain count of prefix sums, query range for each new prefix
- Dynamic Node Creation: Reduces memory for sparse segment trees
Edge Cases
- Single element:
nums = [0],lower = 0,upper = 0→ return1 - All negative:
nums = [-2,-1],lower = -3,upper = -1→ count valid ranges - Large numbers: Use
long longto prevent overflow - Empty ranges: Handle cases where no valid ranges exist
- Overflow prevention: Prefix sums can exceed
intrange
Common Mistakes
- Integer overflow: Not using
long longfor prefix sums// WRONG: vector<int> prefix(n + 1, 0); // ❌ May overflow - Off-by-one errors: Incorrect prefix sum indexing
- Missing prefix[0]: Forgetting to include empty prefix (sum = 0)
- Wrong range: Confusing
prefix[j] - upperandprefix[j] - lower - Memory leaks: Not managing segment tree nodes properly (though solution doesn’t delete)
Comparison of Approaches
| Approach | Time | Space | Pros | Cons |
|---|---|---|---|---|
| Divide & Conquer | O(n log n) | O(n) | Simple, stable | Requires sorting |
| Segment Tree | O(n log U) | O(n log U) | Online processing | More complex, memory overhead |
Related Problems
- LC 327: Count of Range Sum - This problem
- LC 315: Count of Smaller Numbers After Self - Similar divide & conquer approach
- LC 493: Reverse Pairs - Count pairs with condition
- LC 307: Range Sum Query - Mutable - Segment tree for range sum
- LC 303: Range Sum Query - Immutable - Prefix sum basics
This problem demonstrates divide and conquer and segment tree techniques for counting pairs satisfying a range condition. The key transformation is converting subarray sums to prefix sum differences, making it easier to count valid pairs efficiently.