2143. Choose Numbers From Two Arrays in Range 🔒
Problem Description
You have two arrays nums1
and nums2
, both of length n
and 0-indexed.
A range [l, r]
(where 0 <= l <= r < n
) is called balanced if:
- For each index
i
in the range[l, r]
, you select eithernums1[i]
ornums2[i]
- The sum of all values selected from
nums1
equals the sum of all values selected fromnums2
- If no values are selected from an array, its sum is considered 0
Two balanced ranges are considered different if:
- They have different start points (
l1 != l2
), OR - They have different end points (
r1 != r2
), OR - For at least one index
i
, one range picks fromnums1[i]
while the other picks fromnums2[i]
(or vice versa)
The task is to count the total number of different balanced ranges possible. Since this number can be very large, return the result modulo 10^9 + 7
.
For example, if you have a range [0, 2]
with nums1 = [1, 2, 3]
and nums2 = [3, 2, 1]
, you could:
- Pick
nums1[0] = 1
,nums2[1] = 2
, andnums1[2] = 3
, giving sums of 4 and 2 (not balanced) - Pick
nums2[0] = 3
,nums1[1] = 2
, andnums2[2] = 1
, giving sums of 2 and 4 (not balanced) - Pick
nums1[0] = 1
,nums1[1] = 2
, andnums2[2] = 1
, giving sums of 3 and 1 (not balanced) - And so on...
The challenge is to efficiently count all possible balanced configurations across all possible ranges.
Intuition
The key insight is to transform this problem into tracking the difference between sums rather than tracking two separate sums. When we pick nums1[i]
, we can think of it as adding nums1[i]
to our difference. When we pick nums2[i]
, we can think of it as subtracting nums2[i]
from our difference. A balanced range occurs when this difference equals 0.
Let's think about this step by step:
- If we pick
nums1[i]
, the difference increases bynums1[i]
- If we pick
nums2[i]
, the difference decreases bynums2[i]
- We want to count all ways to achieve a difference of 0
This naturally leads to a dynamic programming approach where we track all possible difference values we can achieve ending at each position.
We use f[i][j]
to represent the number of ways to achieve a difference of j - s2
when considering subarrays ending at position i
. We offset by s2
(sum of nums2
) to handle negative differences, since array indices must be non-negative.
For each position i
:
- We can start a new subarray here by picking either
nums1[i]
(difference =nums1[i]
) ornums2[i]
(difference =-nums2[i]
) - We can extend previous subarrays ending at
i-1
by:- Picking
nums1[i]
: if the previous difference wasj - nums1[i]
, it becomesj
- Picking
nums2[i]
: if the previous difference wasj + nums2[i]
, it becomesj
- Picking
The answer accumulates by counting all ways to achieve a difference of 0 (stored at index s2
after offset) at each ending position. This counts all balanced ranges because each different way of achieving 0 difference represents a different balanced configuration.
Learn more about Dynamic Programming patterns.
Solution Approach
The implementation uses dynamic programming with a 2D array to track all possible difference values at each position.
Data Structure Setup:
- Create a 2D DP array
f
of sizen × (s1 + s2 + 1)
wheres1 = sum(nums1)
ands2 = sum(nums2)
- The second dimension size
s1 + s2 + 1
ensures we can handle all possible differences from-s2
tos1
- We use
s2
as an offset to handle negative indices (difference of 0 maps to indexs2
)
Algorithm Steps:
-
Initialize for each position
i
:f[i][a + s2] += 1
: Start a new subarray by pickingnums1[i]
, creating differencea
f[i][-b + s2] += 1
: Start a new subarray by pickingnums2[i]
, creating difference-b
-
Extend from previous position (when
i > 0
):- For each possible difference value
j
:- If we pick
nums1[i]
: Add ways fromf[i-1][j-a]
tof[i][j]
- This means: previous difference was
j-a
, addinga
makes itj
- This means: previous difference was
- If we pick
nums2[i]
: Add ways fromf[i-1][j+b]
tof[i][j]
- This means: previous difference was
j+b
, subtractingb
makes itj
- This means: previous difference was
- If we pick
- For each possible difference value
-
Accumulate answer:
- After processing position
i
, addf[i][s2]
to the answer f[i][s2]
represents all ways to achieve difference 0 for subarrays ending at positioni
- After processing position
Boundary Checks:
- Before accessing
f[i-1][j-a]
, ensurej >= a
to avoid underflow - Before accessing
f[i-1][j+b]
, ensurej+b < s1+s2+1
to avoid overflow
Modulo Operation:
- Apply modulo
10^9 + 7
after each addition to prevent integer overflow
The time complexity is O(n × (s1 + s2))
where n
is the array length and s1
, s2
are the sums of the arrays. The space complexity is also O(n × (s1 + s2))
for the DP table.
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 walk through a small example with nums1 = [1, 2, 1]
and nums2 = [2, 1, 2]
.
Initial Setup:
s1 = sum(nums1) = 1 + 2 + 1 = 4
s2 = sum(nums2) = 2 + 1 + 2 = 5
- Create DP array
f
of size3 × 10
(sinces1 + s2 + 1 = 10
) - Offset value is
5
(equal tos2
)
Processing Position 0 (nums1[0]=1, nums2[0]=2):
Starting new subarrays:
- Pick
nums1[0]
: difference = +1, sof[0][1+5] = f[0][6] = 1
- Pick
nums2[0]
: difference = -2, sof[0][-2+5] = f[0][3] = 1
Check for balanced ranges: f[0][5] = 0
(no way to get difference 0 with single element)
- Answer = 0
Processing Position 1 (nums1[1]=2, nums2[1]=1):
Starting new subarrays:
- Pick
nums1[1]
:f[1][2+5] = f[1][7] = 1
- Pick
nums2[1]
:f[1][-1+5] = f[1][4] = 1
Extending from position 0:
- From
f[0][6]=1
(previous difference was 1):- Pick
nums1[1]
: difference becomes 1+2=3, sof[1][3+5] = f[1][8] += 1
- Pick
nums2[1]
: difference becomes 1-1=0, sof[1][0+5] = f[1][5] += 1
✓
- Pick
- From
f[0][3]=1
(previous difference was -2):- Pick
nums1[1]
: difference becomes -2+2=0, sof[1][0+5] = f[1][5] += 1
✓ - Pick
nums2[1]
: difference becomes -2-1=-3, sof[1][-3+5] = f[1][2] += 1
- Pick
After position 1: f[1][5] = 2
(two balanced ranges found)
- Answer = 0 + 2 = 2
Processing Position 2 (nums1[2]=1, nums2[2]=2):
Starting new subarrays:
- Pick
nums1[2]
:f[2][1+5] = f[2][6] = 1
- Pick
nums2[2]
:f[2][-2+5] = f[2][3] = 1
Extending from position 1:
- From
f[1][7]=1
: extend tof[2][8]
andf[2][5]
- From
f[1][4]=1
: extend tof[2][5]
andf[2][2]
- From
f[1][8]=1
: extend tof[2][9]
andf[2][6]
- From
f[1][5]=2
: extend tof[2][6]
andf[2][3]
- From
f[1][2]=1
: extend tof[2][3]
andf[2][0]
After all extensions: f[2][5] = 2
(two more balanced ranges found)
- Answer = 2 + 2 = 4
Final Result: 4 balanced ranges total
The balanced ranges are:
- Range [1,1]: pick nums2[1]=1 (sum from nums1=0, sum from nums2=0, but we treat empty as 0)
- Range [0,1]: pick nums1[0]=1 and nums2[1]=1 (sums: 1 and 1)
- Range [0,1]: pick nums2[0]=2 and nums1[1]=2 (sums: 2 and 2)
- Range [1,2]: pick nums1[1]=2 and nums2[2]=2 (sums: 2 and 2)
Solution Implementation
1class Solution:
2 def countSubranges(self, nums1: List[int], nums2: List[int]) -> int:
3 n = len(nums1)
4 total_sum1, total_sum2 = sum(nums1), sum(nums2)
5
6 # DP table: dp[i][j] represents the number of ways to achieve
7 # a difference of (j - total_sum2) between sum of elements from nums1
8 # and sum of elements from nums2, ending at index i
9 # We offset by total_sum2 to handle negative differences
10 max_diff_range = total_sum1 + total_sum2 + 1
11 dp = [[0] * max_diff_range for _ in range(n)]
12
13 result = 0
14 MOD = 10**9 + 7
15
16 for i in range(n):
17 current_val1, current_val2 = nums1[i], nums2[i]
18
19 # Base case: start a new subrange at index i
20 # Taking element from nums1 adds current_val1 to the difference
21 dp[i][current_val1 + total_sum2] += 1
22 # Taking element from nums2 subtracts current_val2 from the difference
23 dp[i][-current_val2 + total_sum2] += 1
24
25 # Transition: extend subarrays ending at i-1 to include element at i
26 if i > 0:
27 for diff in range(max_diff_range):
28 # Extend by taking element from nums1 (adds to difference)
29 if diff >= current_val1:
30 dp[i][diff] = (dp[i][diff] + dp[i - 1][diff - current_val1]) % MOD
31
32 # Extend by taking element from nums2 (subtracts from difference)
33 if diff + current_val2 < max_diff_range:
34 dp[i][diff] = (dp[i][diff] + dp[i - 1][diff + current_val2]) % MOD
35
36 # Count subarrays ending at i where sum1 equals sum2 (difference = 0)
37 # The offset position for difference 0 is total_sum2
38 result = (result + dp[i][total_sum2]) % MOD
39
40 return result
41
1class Solution {
2 public int countSubranges(int[] nums1, int[] nums2) {
3 int n = nums1.length;
4
5 // Calculate total sums for offset calculation
6 int totalSum1 = Arrays.stream(nums1).sum();
7 int totalSum2 = Arrays.stream(nums2).sum();
8
9 // dp[i][j] represents the number of subarrays ending at index i
10 // with difference (sum1 - sum2) equal to (j - totalSum2)
11 // We use totalSum2 as offset to handle negative differences
12 int[][] dp = new int[n][totalSum1 + totalSum2 + 1];
13
14 int result = 0;
15 final int MOD = (int) 1e9 + 7;
16
17 for (int i = 0; i < n; ++i) {
18 int currentNum1 = nums1[i];
19 int currentNum2 = nums2[i];
20
21 // Base case: subarray containing only element at index i
22 // When taking nums1[i], difference is +currentNum1
23 dp[i][currentNum1 + totalSum2]++;
24 // When taking nums2[i], difference is -currentNum2
25 dp[i][-currentNum2 + totalSum2]++;
26
27 // Extend subarrays from previous index
28 if (i > 0) {
29 for (int diff = 0; diff <= totalSum1 + totalSum2; ++diff) {
30 // Extend by adding nums1[i] (increases difference by currentNum1)
31 if (diff >= currentNum1) {
32 dp[i][diff] = (dp[i][diff] + dp[i - 1][diff - currentNum1]) % MOD;
33 }
34 // Extend by adding nums2[i] (decreases difference by currentNum2)
35 if (diff + currentNum2 <= totalSum1 + totalSum2) {
36 dp[i][diff] = (dp[i][diff] + dp[i - 1][diff + currentNum2]) % MOD;
37 }
38 }
39 }
40
41 // Count subarrays ending at index i where sum1 equals sum2
42 // This happens when difference is 0, which maps to index totalSum2
43 result = (result + dp[i][totalSum2]) % MOD;
44 }
45
46 return result;
47 }
48}
49
1class Solution {
2public:
3 int countSubranges(vector<int>& nums1, vector<int>& nums2) {
4 int n = nums1.size();
5
6 // Calculate total sums of both arrays
7 int totalSum1 = accumulate(nums1.begin(), nums1.end(), 0);
8 int totalSum2 = accumulate(nums2.begin(), nums2.end(), 0);
9
10 // DP table: dp[i][j] represents the number of ways to reach difference j
11 // at position i (difference = sum1 - sum2)
12 // We offset by totalSum2 to handle negative differences
13 int dp[n][totalSum1 + totalSum2 + 1];
14 memset(dp, 0, sizeof(dp));
15
16 int result = 0;
17 const int MOD = 1e9 + 7;
18
19 // Process each position
20 for (int i = 0; i < n; ++i) {
21 int val1 = nums1[i];
22 int val2 = nums2[i];
23
24 // Initialize: start new subarray from position i
25 // Taking from nums1 increases difference by val1
26 dp[i][val1 + totalSum2]++;
27 // Taking from nums2 decreases difference by val2
28 dp[i][-val2 + totalSum2]++;
29
30 // Extend subarrays from previous position
31 if (i > 0) {
32 for (int diff = 0; diff <= totalSum1 + totalSum2; ++diff) {
33 // Extend by taking nums1[i] (increases difference)
34 if (diff >= val1) {
35 dp[i][diff] = (dp[i][diff] + dp[i - 1][diff - val1]) % MOD;
36 }
37 // Extend by taking nums2[i] (decreases difference)
38 if (diff + val2 <= totalSum1 + totalSum2) {
39 dp[i][diff] = (dp[i][diff] + dp[i - 1][diff + val2]) % MOD;
40 }
41 }
42 }
43
44 // Count subarrays ending at position i with equal sums
45 // (difference = 0, offset by totalSum2)
46 result = (result + dp[i][totalSum2]) % MOD;
47 }
48
49 return result;
50 }
51};
52
1/**
2 * Counts the number of subarrays where the sum from nums1 equals the sum from nums2
3 * @param nums1 - First array of numbers
4 * @param nums2 - Second array of numbers
5 * @returns Number of valid subarrays modulo 10^9 + 7
6 */
7function countSubranges(nums1: number[], nums2: number[]): number {
8 const arrayLength: number = nums1.length;
9 const sum1: number = nums1.reduce((accumulator, current) => accumulator + current, 0);
10 const sum2: number = nums2.reduce((accumulator, current) => accumulator + current, 0);
11
12 // Dynamic programming table
13 // dp[i][j] represents the number of ways to reach difference j at position i
14 // The offset sum2 is used to handle negative differences
15 const dp: number[][] = Array(arrayLength)
16 .fill(0)
17 .map(() => Array(sum1 + sum2 + 1).fill(0));
18
19 const MODULO: number = 1e9 + 7;
20 let result: number = 0;
21
22 for (let position = 0; position < arrayLength; position++) {
23 const valueFromNums1: number = nums1[position];
24 const valueFromNums2: number = nums2[position];
25
26 // Initialize subarrays starting at current position
27 // Adding valueFromNums1 increases the difference
28 dp[position][valueFromNums1 + sum2]++;
29 // Subtracting valueFromNums2 decreases the difference
30 dp[position][-valueFromNums2 + sum2]++;
31
32 // Extend subarrays from previous position
33 if (position > 0) {
34 for (let difference = 0; difference <= sum1 + sum2; difference++) {
35 // Extend by adding current element from nums1
36 if (difference >= valueFromNums1) {
37 dp[position][difference] = (dp[position][difference] +
38 dp[position - 1][difference - valueFromNums1]) % MODULO;
39 }
40 // Extend by adding current element from nums2
41 if (difference + valueFromNums2 <= sum1 + sum2) {
42 dp[position][difference] = (dp[position][difference] +
43 dp[position - 1][difference + valueFromNums2]) % MODULO;
44 }
45 }
46 }
47
48 // Add count of subarrays ending at current position with zero difference
49 result = (result + dp[position][sum2]) % MODULO;
50 }
51
52 return result;
53}
54
Time and Space Complexity
Time Complexity: O(n * (s1 + s2))
where n
is the length of the input arrays, s1
is the sum of all elements in nums1
, and s2
is the sum of all elements in nums2
.
- The outer loop iterates through all
n
elements of the arrays - For each iteration
i
, the inner loop runs through all possible sum states from0
tos1 + s2
, which isO(s1 + s2)
operations - The operations inside the inner loop (checking conditions, updating DP values) are all
O(1)
- Therefore, the total time complexity is
O(n * (s1 + s2))
Space Complexity: O(n * (s1 + s2))
- The 2D DP array
f
has dimensionsn × (s1 + s2 + 1)
, which requiresO(n * (s1 + s2))
space - Other variables like
ans
,mod
,i
,j
,a
,b
useO(1)
space - The dominant factor is the DP array, making the overall space complexity
O(n * (s1 + s2))
Note: In the worst case where all elements have large values, s1
and s2
could be proportional to n * max_value
, making both complexities potentially pseudo-polynomial rather than truly polynomial in the input size.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Index Offset Confusion
The most common pitfall is misunderstanding how the offset works for handling negative differences. The DP table uses total_sum2
as an offset to map difference values to valid array indices.
Pitfall Example:
# Incorrect: Forgetting to apply offset consistently dp[i][current_val1] += 1 # Wrong! This doesn't account for the offset dp[i][-current_val2] += 1 # Wrong! Negative index without offset
Solution:
Always apply the offset total_sum2
when accessing the DP table:
# Correct: Apply offset for both positive and negative differences dp[i][current_val1 + total_sum2] += 1 dp[i][-current_val2 + total_sum2] += 1
2. Boundary Check Errors
Another critical pitfall is not properly checking boundaries when extending subarrays, which can lead to index out of bounds errors.
Pitfall Example:
# Incorrect: No boundary checks
for diff in range(max_diff_range):
dp[i][diff] += dp[i-1][diff - current_val1] # May underflow!
dp[i][diff] += dp[i-1][diff + current_val2] # May overflow!
Solution: Always validate that indices stay within bounds:
# Correct: Check boundaries before accessing if diff >= current_val1: dp[i][diff] = (dp[i][diff] + dp[i-1][diff - current_val1]) % MOD if diff + current_val2 < max_diff_range: dp[i][diff] = (dp[i][diff] + dp[i-1][diff + current_val2]) % MOD
3. Incorrect Difference Calculation
A subtle pitfall is confusing how picking from each array affects the difference.
Pitfall Example:
# Incorrect: Wrong sign for difference updates dp[i][current_val2 + total_sum2] += 1 # Wrong! nums2 should subtract dp[i][-current_val1 + total_sum2] += 1 # Wrong! nums1 should add
Solution: Remember the convention:
- Picking from
nums1[i]
addsnums1[i]
to the difference - Picking from
nums2[i]
subtractsnums2[i]
from the difference
4. Memory Optimization Missed
While not incorrect, using a full 2D array can be memory-intensive. Since we only need the previous row to compute the current row, we can optimize space.
Space-Optimized Solution:
# Use two 1D arrays instead of a 2D array
prev_dp = [0] * max_diff_range
curr_dp = [0] * max_diff_range
for i in range(n):
curr_dp = [0] * max_diff_range
# Base cases
curr_dp[current_val1 + total_sum2] += 1
curr_dp[-current_val2 + total_sum2] += 1
# Transitions from previous
if i > 0:
for diff in range(max_diff_range):
if diff >= current_val1:
curr_dp[diff] = (curr_dp[diff] + prev_dp[diff - current_val1]) % MOD
if diff + current_val2 < max_diff_range:
curr_dp[diff] = (curr_dp[diff] + prev_dp[diff + current_val2]) % MOD
result = (result + curr_dp[total_sum2]) % MOD
prev_dp = curr_dp
This reduces space complexity from O(n × (s1 + s2)) to O(s1 + s2).
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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!