3250. Find the Count of Monotonic Pairs I
Problem Description
You are given an array of positive integers nums
of length n
.
We call a pair of non-negative integer arrays (arr1, arr2)
monotonic if:
- The lengths of both arrays are
n
. arr1
is monotonically non-decreasing, in other words,arr1[0] <= arr1[1] <= ... <= arr1[n - 1]
.arr2
is monotonically non-increasing, in other words,arr2[0] >= arr2[1] >= ... >= arr2[n - 1]
.arr1[i] + arr2[i] == nums[i]
for all0 <= i <= n - 1
.
Return the count of monotonic pairs.
Since the answer may be very large, return it modulo 10^9 + 7
.
Intuition
To solve this problem, we use a dynamic programming approach to calculate the number of valid monotonic pairs (arr1, arr2)
that satisfy the given conditions.
We define f[i][j]
as the number of monotonic pairs for the subarray [0, ..., i]
where arr1[i] = j
. The idea is to build up solutions for subarrays incrementally and utilize prefix sums for optimization.
Here's a step-by-step breakdown of the approach:
-
Initialization: For the first element, any non-negative integer
j
such that0 <= j <= nums[0]
can be chosen forarr1[0]
. Therefore, we start withf[0][j] = 1
for all validj
. -
Transition: For subsequent elements
i > 0
, the valuef[i][j]
is accumulated from the previous rowf[i-1][j']
. Based on the monotonicity conditions:arr1
is non-decreasing: Thus,j' <= j
.arr2
is non-increasing: Therefore, the constraintnums[i] - j <= nums[i-1] - j'
holds. This gives the relationj' <= min(j, j + nums[i-1] - nums[i])
.
We utilize the prefix sum of
f[i-1]
to quickly compute the number of valid configurations for feature reduction. -
Result Formation: Finally, compute the result for all possible end states
f[n-1][j]
where0 <= j <= nums[n-1]
for the entire array lengthn
.
This dynamic programming approach ensures that we efficiently count all possible monotonic pairs that match the requirements, considering modulo 10^9 + 7
to manage large numbers.
Learn more about Math, Dynamic Programming, Combinatorics and Prefix Sum patterns.
Solution Approach
To solve the problem, we employ a dynamic programming approach combined with prefix sum optimization. This technique helps efficiently compute the number of valid monotonic pairs.
Dynamic Programming + Prefix Sum Optimization
-
Initialization:
- We first determine the length of
nums
asn
and the maximum value innums
asm
. - Set up a
2D
dynamic programming tablef
wheref[i][j]
represents the number of monotonic pairs for the subarray[0, \ldots, i]
witharr1[i] = j
. - Initially fill
f[0][j] = 1
for all0 <= j <= nums[0]
.
- We first determine the length of
-
DP Transition:
- For each subsequent index
i
, calculate prefix sums from the previous rowf[i-1]
for efficient calculation:s = list(accumulate(f[i - 1]))
. - For each possible value
j
forarr1[i]
, updatef[i][j]
using:- Find
k
asmin(j, j + nums[i - 1] - nums[i])
. - If
k
is valid (k >= 0
), setf[i][j] = s[k] % mod
.
- Find
- For each subsequent index
-
Result Calculation:
- Sum all values
f[n-1][j]
where0 <= j <= nums[n-1]
to get the final count of valid pairs. - Return the result modulo
10^9 + 7
.
- Sum all values
This approach utilizes two loops nested over the indices of the array and possible values of arr1
, combined with prefix sum calculation for efficient dynamic programming state updates. It ensures that all conditions for monotonic arrays are respected while counting possibilities.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Consider the array nums = [3, 2, 5]
. We aim to count the number of monotonic pairs (arr1, arr2)
such that:
arr1
is monotonically non-decreasing.arr2
is monotonically non-increasing.arr1[i] + arr2[i] = nums[i]
for alli
.
Step-by-Step Approach
-
Initialization:
- Length of
nums
(n
) is 3. - Dynamic programming table
f
is initialized for each possiblej
where0 <= j <= nums[0]
. - Initialize
f[0][j] = 1
forj = 0, 1, 2, 3
because these are valid values forarr1[0]
that sum witharr2[0]
tonums[0]
.
- Length of
-
DP Transition:
-
For
i = 1
:- Calculate the prefix sum from
f[0]
:s = [1, 2, 3, 4]
. - For each
j
from0
tonums[1] = 2
:- Compute
k = min(j, j + nums[0] - nums[1])
. - For each valid
k
, computef[1][j] = s[k] % mod
.
- Compute
- Key updates:
f[1][0] = s[0] = 1
f[1][1] = s[1] = 2
f[1][2] = s[2] = 3
- Calculate the prefix sum from
-
For
i = 2
:- Calculate the prefix sum from
f[1]
:s = [1, 3, 6]
. - For each
j
from0
tonums[2] = 5
:- Compute
k = min(j, j + nums[1] - nums[2])
. - For each valid
k
, computef[2][j] = s[k] % mod
.
- Compute
- Key updates:
f[2][0] = s[0] = 1
f[2][1] = s[1] = 3
f[2][2] = s[2] = 6
f[2][3] = s[3] = 10
f[2][4] = s[4] = 15
f[2][5] = s[5] = 21
- Calculate the prefix sum from
-
-
Result Calculation:
- Sum up all valid
f[2][j]
where0 <= j <= nums[2]
: - Result =
(1 + 3 + 6 + 10 + 15 + 21) % (10^9 + 7) = 56
.
- Sum up all valid
Thus, the total number of valid monotonic pairs for the given nums
is 56.
Solution Implementation
1from itertools import accumulate
2from typing import List
3
4class Solution:
5 def countOfPairs(self, nums: List[int]) -> int:
6 mod = 10**9 + 7 # Constant to take result modulo
7 n = len(nums) # Length of the input list
8 m = max(nums) # Maximum value in the input list
9
10 # Initializing a 2D list 'f' with zeros,
11 # dimensions [n x (m+1)], for dynamic programming
12 f = [[0] * (m + 1) for _ in range(n)]
13
14 # Base case: Only one way to have no elements exceeding 'nums[0]'
15 for j in range(nums[0] + 1):
16 f[0][j] = 1
17
18 # Fill the DP table
19 for i in range(1, n):
20 # Compute prefix sums for the previous row in our DP table
21 s = list(accumulate(f[i - 1]))
22
23 # Iterate through all possible 'nums[i]' values
24 for j in range(nums[i] + 1):
25 k = min(j, j + nums[i - 1] - nums[i]) # Calculate the boundary
26
27 # If boundary is valid, update current DP cell
28 if k >= 0:
29 f[i][j] = s[k] % mod
30
31 # Calculate the final result by summing all valid end states modulo 'mod'
32 return sum(f[-1][: nums[-1] + 1]) % mod
33
1import java.util.Arrays;
2
3class Solution {
4
5 public int countOfPairs(int[] nums) {
6 final int MOD = (int) 1e9 + 7; // Define the modulo constant for large numbers
7 int n = nums.length; // Length of the input array
8 int maxNum = Arrays.stream(nums).max().getAsInt(); // Find the maximum number in the array
9
10 int[][] dp = new int[n][maxNum + 1]; // Initialize DP array
11 // Initialize the first row of DP table
12 for (int j = 0; j <= nums[0]; ++j) {
13 dp[0][j] = 1;
14 }
15
16 int[] prefixSum = new int[maxNum + 1]; // Array to store prefix sums for optimization
17
18 // Fill the DP table
19 for (int i = 1; i < n; ++i) {
20 // Compute prefix sums based on the previous row
21 prefixSum[0] = dp[i - 1][0];
22 for (int j = 1; j <= maxNum; ++j) {
23 prefixSum[j] = (prefixSum[j - 1] + dp[i - 1][j]) % MOD;
24 }
25
26 // Calculate the DP for current row
27 for (int j = 0; j <= nums[i]; ++j) {
28 int k = Math.min(j, j + nums[i - 1] - nums[i]);
29 if (k >= 0) {
30 dp[i][j] = prefixSum[k];
31 }
32 }
33 }
34
35 // Calculate the final answer by summing up the last row of the DP table
36 int result = 0;
37 for (int j = 0; j <= nums[n - 1]; ++j) {
38 result = (result + dp[n - 1][j]) % MOD;
39 }
40 return result;
41 }
42}
43
1class Solution {
2public:
3 int countOfPairs(vector<int>& nums) {
4 const int mod = 1e9 + 7; // Define the modulus value for result to prevent overflow
5 int n = nums.size(); // Calculate the size of the input vector 'nums'
6 int maxNum = *max_element(nums.begin(), nums.end()); // Find the maximum element in 'nums'
7
8 // Create a 2D vector 'f' for dynamic programming, initialized with zeros
9 vector<vector<int>> f(n, vector<int>(maxNum + 1, 0));
10
11 // Initialize the first row with 1 up to nums[0] since the first number can form pairs with itself
12 for (int j = 0; j <= nums[0]; ++j) {
13 f[0][j] = 1;
14 }
15
16 // Create a vector 'g' to hold cumulative sums of the previous row for optimized calculations
17 vector<int> g(maxNum + 1, 0);
18
19 // Fill the DP table using previously computed results
20 for (int i = 1; i < n; ++i) {
21 g[0] = f[i - 1][0]; // Initialize the first value of g with the previous row's first element
22
23 // Fill 'g' with cumulative sum of the previous row, modulated
24 for (int j = 1; j <= maxNum; ++j) {
25 g[j] = (g[j - 1] + f[i - 1][j]) % mod;
26 }
27
28 // Calculate the DP results for the current row
29 for (int j = 0; j <= nums[i]; ++j) {
30 int k = min(j, j + nums[i - 1] - nums[i]); // Determine the farthest index in the previous row to consider
31
32 if (k >= 0) {
33 f[i][j] = g[k]; // Assign the cumulative sum up to 'k' to the current position
34 }
35 }
36 }
37
38 int result = 0; // Initialize the result to accumulate the final number of pairs
39
40 // Sum all possible pair counts from the last DP row to get the final answer
41 for (int j = 0; j <= nums[n - 1]; ++j) {
42 result = (result + f[n - 1][j]) % mod;
43 }
44
45 return result; // Return the total count of valid pairs
46 }
47};
48
1function countOfPairs(nums: number[]): number {
2 const mod = 1e9 + 7; // Modulo value for large number computations to avoid overflow
3 const n = nums.length; // Length of the input array
4 const m = Math.max(...nums); // Maximum value in the input array
5
6 // Initialize a 2D array `f` to hold the count of pairs
7 // `f[i][j]` represents the number of valid ways to achieve a sum 'j' using the first 'i' numbers
8 const f: number[][] = Array.from({ length: n }, () => Array(m + 1).fill(0));
9
10 // Base case: with the first number, we can only achieve sums from 0 to nums[0]
11 for (let j = 0; j <= nums[0]; j++) {
12 f[0][j] = 1;
13 }
14
15 // Temporary array `g` to manipulate rolling sums efficiently for dynamic programming
16 const g: number[] = Array(m + 1).fill(0);
17
18 // Iterate through each number from the second one to the last
19 for (let i = 1; i < n; i++) {
20 // Initialize the rolling sum array `g`
21 g[0] = f[i - 1][0];
22
23 // Populate `g` with cumulative sums to facilitate quick access
24 for (let j = 1; j <= m; j++) {
25 g[j] = (g[j - 1] + f[i - 1][j]) % mod;
26 }
27
28 // Update `f[i][j]` based on the previous results and the rules given
29 for (let j = 0; j <= nums[i]; j++) {
30 const k = Math.min(j, j + nums[i - 1] - nums[i]); // Calculate the valid boundary based on current and previous values
31 if (k >= 0) {
32 // If valid, then count the sums that can form `j` using the available previous results
33 f[i][j] = g[k];
34 }
35 }
36 }
37
38 let ans = 0; // To store the final answer
39 // Sum up all counts for the last number in `nums` to get the total number of valid pairs
40 for (let j = 0; j <= nums[n - 1]; j++) {
41 ans = (ans + f[n - 1][j]) % mod;
42 }
43
44 return ans; // Return the total count of pairs modulo `mod`
45}
46
Time and Space Complexity
The time complexity of the code is O(n * m)
, where n
is the length of the array nums
, and m
is the maximum value in the array nums
. The outer loop iterates n
times, and for each element, work proportional to m
is performed, specifically initializing the DP table and carrying out prefix sum operations.
The space complexity is also O(n * m)
due to the creation of a 2D list f
with dimensions [n][m + 1]
, used to store intermediate results for dynamic programming.
Learn more about how to find time and space complexity quickly.
How many times is a tree node visited in a depth first search?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
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
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
Want a Structured Path to Master System Design Too? Don’t Miss This!