Find All Possible Stable Binary Arrays I
MedPrompt
You are given 3 positive integers zero, one, and limit.
A binary array arr is called stable if:
- The number of occurrences of 0 in
arris exactlyzero. - The number of occurrences of 1 in
arris exactlyone. - Each subarray of
arrwith a size greater thanlimitmust contain both 0 and 1.
Return the total number of stable binary arrays.
Since the answer may be very large, return it modulo 109 + 7.
Example 1:
Input: zero = 1, one = 1, limit = 2
Output: 2
Explanation:
The two possible stable binary arrays are [1,0] and [0,1], as both arrays have a single 0 and a single 1, and no subarray has a length greater than 2.
Example 2:
Input: zero = 1, one = 2, limit = 1
Output: 1
Explanation:
The only possible stable binary array is [1,0,1].
Note that the binary arrays [1,1,0] and [0,1,1] have subarrays of length 2 with identical elements, hence, they are not stable.
Example 3:
Input: zero = 3, one = 3, limit = 2
Output: 14
Explanation:
All the possible stable binary arrays are [0,0,1,0,1,1], [0,0,1,1,0,1], [0,1,0,0,1,1], [0,1,0,1,0,1], [0,1,0,1,1,0], [0,1,1,0,0,1], [0,1,1,0,1,0], [1,0,0,1,0,1], [1,0,0,1,1,0], [1,0,1,0,0,1], [1,0,1,0,1,0], [1,0,1,1,0,0], [1,1,0,0,1,0], and [1,1,0,1,0,0].
Constraints:
1 <= zero, one, limit <= 200
Approaches
2 approaches with complexity analysis and trade-offs.
This problem can be solved using dynamic programming. We build the stable arrays by adding one element at a time, keeping track of the number of zeros and ones used, and what the last element was. This helps us enforce the limit constraint on consecutive identical elements.
Algorithm
- Let
dp[i][j][0]be the number of stable binary arrays usingizeros andjones that end with a '0'. - Let
dp[i][j][1]be the number of stable binary arrays usingizeros andjones that end with a '1'. - The state transitions are based on the idea that an array ending in a block of
kidentical elements must have been formed by appending that block to an array ending in the opposite element. - Transition for
dp[i][j][0]: An array withizeros andjones ending in '0' must be formed by appending a block ofkzeros (where1 <= k <= limit) to a valid array withi-kzeros andjones that ends in '1'.dp[i][j][0] = sum(dp[i-k][j][1] for k in 1..min(i, limit)) - Transition for
dp[i][j][1]: Similarly, an array withizeros andjones ending in '1' is formed by appending a block ofkones (1 <= k <= limit) to a valid array withizeros andj-kones that ends in '0'.dp[i][j][1] = sum(dp[i][j-k][0] for k in 1..min(j, limit)) - Base Cases: A block of
kzeros can be a valid starting array, sodp[k][0][0] = 1for1 <= k <= limit. Similarly,dp[0][k][1] = 1for1 <= k <= limit. - Algorithm Steps:
- Initialize a 3D DP table
dp[zero+1][one+1][2]with zeros. - Set the base cases:
dp[k][0][0] = 1for1 <= k <= limitanddp[0][k][1] = 1for1 <= k <= limit. - Iterate
ifrom 1 tozeroandjfrom 1 toone. - Inside the loops, calculate
dp[i][j][0]anddp[i][j][1]using the transition formulas, which involve an inner loop up tolimit. Remember to take modulo10^9 + 7at each addition. - The final answer is
(dp[zero][one][0] + dp[zero][one][1]) % MOD.
- Initialize a 3D DP table
Walkthrough
A straightforward dynamic programming approach involves defining a state that captures the necessary information to build a valid array. The state should include the number of zeros used, the number of ones used, and the last element added to the array.
Let dp[i][j][0] be the number of stable binary arrays using i zeros and j ones that end with a '0'.
Let dp[i][j][1] be the number of stable binary arrays using i zeros and j ones that end with a '1'.
To compute dp[i][j][0], we consider how such an array could be formed. It must end with a block of k zeros, where 1 <= k <= limit. This block must be appended to a valid array of i-k zeros and j ones that ends with a '1'. Thus, we sum up the possibilities over all valid k.
dp[i][j][0] = (dp[i-1][j][1] + dp[i-2][j][1] + ... + dp[i-min(i, limit)][j][1]) % MOD
Similarly, for dp[i][j][1]:
dp[i][j][1] = (dp[i][j-1][0] + dp[i][j-2][0] + ... + dp[i][j-min(j, limit)][0]) % MOD
The base cases are arrays that consist of only one type of element. An array of k zeros (with 1 <= k <= limit) is a valid base case, as is an array of k ones.
We can then fill the DP table iteratively. The final answer is the sum of ways to form an array with zero zeros and one ones, ending in either '0' or '1'.
class Solution { public int numberOfStableArrays(int zero, int one, int limit) { int MOD = 1_000_000_007; long[][][] dp = new long[zero + 1][one + 1][2]; // Base cases: arrays consisting of only 0s or only 1s for (int i = 1; i <= Math.min(zero, limit); i++) { dp[i][0][0] = 1; } for (int j = 1; j <= Math.min(one, limit); j++) { dp[0][j][1] = 1; } for (int i = 1; i <= zero; i++) { for (int j = 1; j <= one; j++) { // Calculate dp[i][j][0]: ends with 0 // This is formed by appending k zeros to an array ending in 1. // Sum up dp[i-k][j][1] for k from 1 to limit. for (int k = 1; k <= Math.min(i, limit); k++) { dp[i][j][0] = (dp[i][j][0] + dp[i - k][j][1]) % MOD; } // Calculate dp[i][j][1]: ends with 1 // This is formed by appending k ones to an array ending in 0. // Sum up dp[i][j-k][0] for k from 1 to limit. for (int k = 1; k <= Math.min(j, limit); k++) { dp[i][j][1] = (dp[i][j][1] + dp[i][j - k][0]) % MOD; } } } return (int) ((dp[zero][one][0] + dp[zero][one][1]) % MOD); }}Complexity
Time
O(zero * one * limit). We have nested loops for `i` (up to `zero`), `j` (up to `one`), and an inner summation loop `k` (up to `limit`).
Space
O(zero * one). We use a 3D DP table of size `(zero + 1) x (one + 1) x 2`.
Trade-offs
Pros
Relatively straightforward to conceptualize from the problem definition.
Cons
The time complexity might be too high for larger constraints, although it might pass for the given constraints.
The previous DP approach involves a summation loop that can be optimized. The sums sum(dp[i-k][j][1]) and sum(dp[i][j-k][0]) are essentially sliding window sums. We can precompute these sums or calculate them in O(1) time using prefix sums, which significantly improves the time complexity.
Algorithm
- The DP state
dp[i][j][k]remains the same as the naive approach. - The key observation is that the summations in the transitions are over a sliding window.
dp[i][j][0] = sum(dp[i-k][j][1] for k in 1..limit)dp[i][j][1] = sum(dp[i][j-k][0] for k in 1..limit)
- These sums can be computed in O(1) time using prefix sums.
- Define
prefix0[i][j] = sum(dp[i][k][0] for k in 0..j). - Define
prefix1[i][j] = sum(dp[k][j][1] for k in 0..i). - The transitions become:
dp[i][j][0] = (prefix1[i-1][j] - prefix1[i-limit-1][j]) % MODdp[i][j][1] = (prefix0[i][j-1] - prefix0[i][j-limit-1]) % MOD(with appropriate boundary checks for negative indices).
- Algorithm Steps:
- Initialize
dp[zero+1][one+1][2],prefix0[zero+1][one+1], andprefix1[zero+1][one+1]tables. - Set the same base cases as the naive approach:
dp[k][0][0] = 1for1 <= k <= limitanddp[0][k][1] = 1for1 <= k <= limit. - Initialize the prefix sum tables for the base cases (i.e., for
i=0andj=0). - Iterate
ifrom 1 tozeroandjfrom 1 toone. - Calculate
dp[i][j][0]anddp[i][j][1]in O(1) time using the prefix sum tables. - Update the prefix sum tables
prefix0[i][j]andprefix1[i][j]with the newly computeddpvalues. - The final answer is
(dp[zero][one][0] + dp[zero][one][1]) % MOD.
- Initialize
Walkthrough
The O(zero * one * limit) complexity of the naive DP approach comes from the inner loop used for summation. We can eliminate this loop by recognizing that the sum is over a contiguous range. This is a classic pattern that can be optimized using prefix sums or a sliding window technique.
Let's maintain two additional 2D arrays for prefix sums:
prefix0[i][j]will store the sum ofdp[i][k][0]forkfrom 0 toj.prefix1[i][j]will store the sum ofdp[k][j][1]forkfrom 0 toi.
With these prefix sum tables, the transitions can be rewritten:
dp[i][j][0] = (prefix1[i-1][j] - (i > limit ? prefix1[i-limit-1][j] : 0) + MOD) % MOD
dp[i][j][1] = (prefix0[i][j-1] - (j > limit ? prefix0[i][j-limit-1] : 0) + MOD) % MOD
We iterate through i from 0 to zero and j from 0 to one. In each step (i, j), we first calculate dp[i][j][0] and dp[i][j][1] using the pre-calculated prefix sums from previous steps. Then, we update the prefix sum tables prefix0[i][j] and prefix1[i][j] to include the new dp values, making them available for subsequent calculations. This reduces the computation for each state to constant time.
class Solution { public int numberOfStableArrays(int zero, int one, int limit) { int MOD = 1_000_000_007; long[][][] dp = new long[zero + 1][one + 1][2]; long[][] prefix0 = new long[zero + 1][one + 1]; // prefix sum for dp[i][...][0] on j long[][] prefix1 = new long[zero + 1][one + 1]; // prefix sum for dp[...][j][1] on i // Base cases for (int i = 1; i <= Math.min(zero, limit); i++) { dp[i][0][0] = 1; } for (int j = 1; j <= Math.min(one, limit); j++) { dp[0][j][1] = 1; } // Initialize prefix sums for i=0 and j=0 for (int i = 0; i <= zero; i++) { prefix1[i][0] = dp[i][0][1]; if (i > 0) { prefix1[i][0] = (prefix1[i][0] + prefix1[i - 1][0]) % MOD; } } for (int j = 0; j <= one; j++) { prefix0[0][j] = dp[0][j][0]; if (j > 0) { prefix0[0][j] = (prefix0[0][j] + prefix0[0][j - 1]) % MOD; } } // Fill DP table using prefix sums for (int i = 1; i <= zero; i++) { for (int j = 1; j <= one; j++) { // Calculate dp[i][j][0] long ways_from_1s = (prefix1[i - 1][j] - (i - limit - 1 >= 0 ? prefix1[i - limit - 1][j] : 0) + MOD) % MOD; dp[i][j][0] = ways_from_1s; // Calculate dp[i][j][1] long ways_from_0s = (prefix0[i][j - 1] - (j - limit - 1 >= 0 ? prefix0[i][j - limit - 1] : 0) + MOD) % MOD; dp[i][j][1] = ways_from_0s; // Update prefix sums for the current state prefix0[i][j] = (prefix0[i][j - 1] + dp[i][j][0]) % MOD; prefix1[i][j] = (prefix1[i - 1][j] + dp[i][j][1]) % MOD; } } return (int) ((dp[zero][one][0] + dp[zero][one][1]) % MOD); }}Complexity
Time
O(zero * one). We iterate through the `(zero+1) x (one+1)` grid, and each DP state is computed in O(1) using the prefix sum tables.
Space
O(zero * one). We use three tables (`dp`, `prefix0`, `prefix1`) of size proportional to `zero * one`.
Trade-offs
Pros
Highly efficient and optimal in terms of time complexity.
Comfortably passes the given constraints.
Cons
Requires more space due to helper tables for prefix sums.
The logic is slightly more complex to derive and implement correctly.
Solutions
Solution
class Solution {private final int mod = (int)1 e9 + 7;private Long[][][] f;private int limit;public int numberOfStableArrays(int zero, int one, int limit) { f = new Long[zero + 1][one + 1][2]; this.limit = limit; return (int)((dfs(zero, one, 0) + dfs(zero, one, 1)) % mod); }private long dfs(int i, int j, int k) { if (i < 0 || j < 0) { return 0; } if (i == 0) { return k == 1 && j <= limit ? 1 : 0; } if (j == 0) { return k == 0 && i <= limit ? 1 : 0; } if (f[i][j][k] != null) { return f[i][j][k]; } if (k == 0) { f[i][j][k] = (dfs(i - 1, j, 0) + dfs(i - 1, j, 1) - dfs(i - limit - 1, j, 1) + mod) % mod; } else { f[i][j][k] = (dfs(i, j - 1, 0) + dfs(i, j - 1, 1) - dfs(i, j - limit - 1, 0) + mod) % mod; } return f[i][j][k]; }}using ll = long long ; class Solution { public: int numberOfStableArrays ( int zero , int one , int limit ) { this -> limit = limit ; f = vector < vector < array < ll , 2 >>> ( zero + 1 , vector < array < ll , 2 >> ( one + 1 , { - 1 , - 1 })); return ( dfs ( zero , one , 0 ) + dfs ( zero , one , 1 )) % mod ; } private: const int mod = 1e9 + 7 ; int limit ; vector < vector < array < ll , 2 >>> f ; ll dfs ( int i , int j , int k ) { if ( i < 0 || j < 0 ) { return 0 ; } if ( i == 0 ) { return k == 1 && j <= limit ; } if ( j == 0 ) { return k == 0 && i <= limit ; } ll & res = f [ i ][ j ][ k ]; if ( res != - 1 ) { return res ; } if ( k == 0 ) { res = ( dfs ( i - 1 , j , 0 ) + dfs ( i - 1 , j , 1 ) - dfs ( i - limit - 1 , j , 1 ) + mod ) % mod ; } else { res = ( dfs ( i , j - 1 , 0 ) + dfs ( i , j - 1 , 1 ) - dfs ( i , j - limit - 1 , 0 ) + mod ) % mod ; } return res ; } };class Solution: def numberOfStableArrays(self, zero: int, one: int, limit: int) -> int: @ cache def dfs(i: int, j: int, k: int) -> int: if i == 0: return int(k == 1 and j <= limit) if j == 0: return int(k == 0 and i <= limit) if k == 0: return (dfs(i - 1, j, 0) + dfs(i - 1, j, 1) - (0 if i - limit - 1 < 0 else dfs(i - limit - 1, j, 1))) return (dfs(i, j - 1, 0) + dfs(i, j - 1, 1) - (0 if j - limit - 1 < 0 else dfs(i, j - limit - 1, 0))) mod = 10 ** 9 + 7 ans = (dfs(zero, one, 0) + dfs(zero, one, 1)) % mod dfs . cache_clear() return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.