Find the Count of Monotonic Pairs I

HARD

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 all 0 <= i <= n - 1.

Return the count of monotonic pairs.

Since the answer may be very large, return it modulo 109 + 7.

 

Example 1:

Input: nums = [2,3,2]

Output: 4

Explanation:

The good pairs are:

  1. ([0, 1, 1], [2, 2, 1])
  2. ([0, 1, 2], [2, 2, 0])
  3. ([0, 2, 2], [2, 1, 0])
  4. ([1, 2, 2], [1, 1, 0])

Example 2:

Input: nums = [5,5,5,5]

Output: 126

 

Constraints:

  • 1 <= n == nums.length <= 2000
  • 1 <= nums[i] <= 50

Approaches

Checkout 3 different approaches to solve Find the Count of Monotonic Pairs I. Click on different approaches to view the approach and algorithm in detail.

Dynamic Programming with Prefix Sums

This approach optimizes the naive DP solution by observing that the inner loop is calculating a sum over a range. Such summations can be computed efficiently using prefix sums. By pre-calculating the prefix sums for each row of the DP table, we can determine the value of any dp[i][j] in constant time, which significantly improves the overall time complexity.

Algorithm

  • The DP state dp[i][j] is the same as the naive approach.
  • The transition dp[i][j] = sum(dp[i-1][k]) is over a contiguous range of k.
  • The upper bound for k is limit = min(nums[i-1], j - max(0, nums[i] - nums[i-1])).
  • We can precompute prefix sums for each row i-1 of the DP table into an array ps[i-1].
  • dp[i][j] can then be found in O(1) time using ps[i-1][limit].
  • This removes the innermost loop from the naive DP approach.

The key insight is that the transition dp[i][j] = sum(dp[i-1][k]) is performed for k in the range [0, limit], where limit = min(nums[i-1], j - max(0, nums[i] - nums[i-1])). This is a prefix sum.

We can maintain a parallel 2D array, ps, where ps[i][x] stores the sum of dp[i][k] for k from 0 to x. After computing the i-1-th row of the dp table, we can compute the i-1-th row of the ps table in O(M) time. Then, when computing the i-th row of dp, each dp[i][j] can be found in O(1) by looking up the precomputed value in ps[i-1], effectively eliminating one loop from the previous approach.

Algorithm Steps:

  1. Create a DP table dp[n][M+1] and a prefix sum table ps[n][M+1].
  2. Base Case (i=0): Fill dp[0] and ps[0] as before.
  3. Transitions (i > 0): For i from 1 to n-1:
    • For each j from 0 to nums[i]:
      • Calculate the upper bound for k: limit = min(nums[i-1], j - max(0, nums[i] - nums[i-1])).
      • If limit >= 0, set dp[i][j] = ps[i-1][limit].
    • Compute the prefix sums for the new dp[i] row and store them in ps[i].
  4. Final Result: The answer is ps[n-1][nums[n-1]].
class Solution {
    public int countMonotonicPairs(int[] nums) {
        int n = nums.length;
        int maxVal = 0;
        for (int num : nums) {
            maxVal = Math.max(maxVal, num);
        }
        long[][] dp = new long[n][maxVal + 1];
        long[][] ps = new long[n][maxVal + 1];
        int MOD = 1_000_000_007;

        // Base case: i = 0
        for (int j = 0; j <= nums[0]; j++) {
            dp[0][j] = 1;
        }
        ps[0][0] = dp[0][0];
        for (int j = 1; j <= maxVal; j++) {
            ps[0][j] = (ps[0][j - 1] + dp[0][j]) % MOD;
        }

        // Fill DP table for i > 0
        for (int i = 1; i < n; i++) {
            long diff = (long)nums[i] - nums[i-1];
            for (int j = 0; j <= nums[i]; j++) {
                long k_upper_bound = j - Math.max(0, diff);
                long limit = Math.min(nums[i-1], k_upper_bound);
                
                if (limit >= 0) {
                    dp[i][j] = ps[i-1][(int)limit];
                }
            }
            
            ps[i][0] = dp[i][0];
            for (int j = 1; j <= maxVal; j++) {
                ps[i][j] = (ps[i][j - 1] + dp[i][j]) % MOD;
            }
        }

        return (int) ps[n - 1][nums[n - 1]];
    }
}

Complexity Analysis

Time Complexity: O(n * M). For each of the `n` states, we perform O(M) work to compute the DP values and their prefix sums.Space Complexity: O(n * M), for the DP and prefix sum tables.

Pros and Cons

Pros:
  • Much faster than the naive DP approach, with a time complexity of O(n * M).
  • Handles the given constraints very efficiently.
Cons:
  • Requires additional space for the prefix sum table, although the asymptotic space complexity remains the same.

Code Solutions

Checking out 3 solutions in different languages for Find the Count of Monotonic Pairs I. Click on different languages to view the code.

class Solution { public int countOfPairs ( int [] nums ) { final int mod = ( int ) 1 e9 + 7 ; int n = nums . length ; int m = Arrays . stream ( nums ). max (). getAsInt (); int [][] f = new int [ n ][ m + 1 ]; for ( int j = 0 ; j <= nums [ 0 ]; ++ j ) { f [ 0 ][ j ] = 1 ; } int [] g = new int [ m + 1 ]; for ( int i = 1 ; i < n ; ++ i ) { g [ 0 ] = f [ i - 1 ][ 0 ]; for ( int j = 1 ; j <= m ; ++ j ) { g [ j ] = ( g [ j - 1 ] + f [ i - 1 ][ j ]) % mod ; } for ( int j = 0 ; j <= nums [ i ]; ++ j ) { int k = Math . min ( j , j + nums [ i - 1 ] - nums [ i ]); if ( k >= 0 ) { f [ i ][ j ] = g [ k ]; } } } int ans = 0 ; for ( int j = 0 ; j <= nums [ n - 1 ]; ++ j ) { ans = ( ans + f [ n - 1 ][ j ]) % mod ; } return ans ; } }

Video Solution

Watch the video walkthrough for Find the Count of Monotonic Pairs I



Patterns:

MathDynamic ProgrammingCombinatoricsPrefix Sum

Data Structures:

Array

Companies:

Subscribe to Scale Engineer newsletter

Learn about System Design, Software Engineering, and interview experiences every week.

No spam, unsubscribe at any time.