Make Array Non-decreasing
MedPrompt
You are given an integer array nums. In one operation, you can select a subarray and replace it with a single element equal to its maximum value.
Return the maximum possible size of the array after performing zero or more operations such that the resulting array is non-decreasing.
Example 1:
Input: nums = [4,2,5,3,5]
Output: 3
Explanation:
One way to achieve the maximum size is:
- Replace subarray
nums[1..2] = [2, 5]with5→[4, 5, 3, 5]. - Replace subarray
nums[2..3] = [3, 5]with5→[4, 5, 5].
The final array [4, 5, 5] is non-decreasing with size 3.
Example 2:
Input: nums = [1,2,3]
Output: 3
Explanation:
No operation is needed as the array [1,2,3] is already non-decreasing.
Constraints:
1 <= nums.length <= 2 * 1051 <= nums[i] <= 2 * 105
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses dynamic programming to build the solution iteratively. We define a DP state dp[i] that stores information about the optimal partition for the prefix nums[0...i]. Specifically, dp[i] will store a pair (size, last_val), representing the maximum size of a valid non-decreasing array and the minimum possible value of the last element for that maximum size.
Algorithm
- Initialize a DP array
dpof sizeN, wheredp[i]will store a pair{max_size, min_last_element_value}for the prefixnums[0...i]. - Set a base case for
i=0:dp[0] = {1, nums[0]}. - Iterate
ifrom1toN-1to computedp[i]. - Inside this loop, initialize
bestSizeto 0 andminLastValto infinity for the currenti. - Start a nested loop, iterating
jfromidown to0. Thisjrepresents the start of the last segmentnums[j...i]. - In the inner loop, maintain
currentMax, the maximum value innums[j...i]. - Get the result for the prefix
nums[0...j-1]. Ifj=0, the previous size is 0 and the previous last value is negative infinity. Otherwise, they aredp[j-1][0]anddp[j-1][1]. - If the previous last value is less than or equal to
currentMax, we have a valid partition. The new size isprevSize + 1. - Update
bestSizeandminLastValfordp[i]if this new partition is better (larger size, or same size with smaller last value). - After the inner loop finishes, store the final
bestSizeandminLastValindp[i]. - The final answer is the size component of
dp[N-1].
Walkthrough
The core idea is to build the solution for nums[0...i] based on the solutions for smaller prefixes nums[0...j-1]. When we consider nums[0...i], the last element of our resulting non-decreasing array must be the maximum of some subarray nums[j...i]. Let this maximum be M. For the resulting array to be non-decreasing, the element before M must be less than or equal to M. This previous element is the last element of an optimal partition for the prefix nums[0...j-1].
This leads to the DP state dp[i] = (s_i, v_i), where s_i is the maximum possible size of the non-decreasing array for the prefix nums[0...i], and v_i is the minimum possible value of the last element among all partitions that achieve this maximum size. Minimizing the last element is a greedy choice that helps in extending the sequence later, as a smaller last element allows more options for the next element.
To compute dp[i], we iterate through all possible split points j (from i down to 0), considering nums[j...i] as the last group. We calculate its maximum m = max(nums[j...i]). We then check if m is greater than or equal to the last value of the optimal partition for nums[0...j-1]. If it is, we have found a candidate partition for nums[0...i]. We do this for all j and pick the one that maximizes the partition size, breaking ties by choosing the one with the minimum last element value.
class Solution { public int makeArrayNonDecreasing(int[] nums) { int n = nums.length; if (n == 0) { return 0; } // dp[i] stores a pair: {max_size, min_last_element_value} for prefix nums[0...i] int[][] dp = new int[n][2]; // Base case: i = 0 dp[0][0] = 1; dp[0][1] = nums[0]; for (int i = 1; i < n; i++) { int bestSize = 0; int minLastVal = Integer.MAX_VALUE; int currentMax = 0; // Iterate backwards for the last segment nums[j...i] for (int j = i; j >= 0; j--) { currentMax = Math.max(currentMax, nums[j]); int prevSize = (j == 0) ? 0 : dp[j - 1][0]; int prevLastVal = (j == 0) ? Integer.MIN_VALUE : dp[j - 1][1]; if (prevLastVal <= currentMax) { int currentSize = prevSize + 1; if (currentSize > bestSize) { bestSize = currentSize; minLastVal = currentMax; } else if (currentSize == bestSize) { minLastVal = Math.min(minLastVal, currentMax); } } } dp[i][0] = bestSize; dp[i][1] = minLastVal; } return dp[n - 1][0]; }}Complexity
Time
O(N^2), where N is the length of `nums`. There are two nested loops. The outer loop runs N times for `i`, and the inner loop runs up to `i+1` times for `j`. The work inside the inner loop is constant time because we maintain the running maximum.
Space
O(N) to store the DP table of size N, where each entry holds a pair of integers.
Trade-offs
Pros
It is a systematic approach that correctly explores the problem space to find the optimal solution.
It serves as a good foundation for understanding the problem before moving to more optimized solutions.
Cons
The O(N^2) time complexity is too slow for the given constraints (N up to 2 * 10^5) and will likely result in a Time Limit Exceeded (TLE) error on most platforms.
Solutions
Solution
class Solution {public int maximumPossibleSize(int[] nums) { int ans = 0, mx = 0; for (int x : nums) { if (mx <= x) { ++ans; mx = x; } } return ans; }}Video 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.