Make Array Non-decreasing
MEDIUMDescription
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
Checkout 2 different approaches to solve Make Array Non-decreasing. Click on different approaches to view the approach and algorithm in detail.
Dynamic Programming
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].
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 Analysis
Pros and Cons
- 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.
- 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.
Code Solutions
Checking out 3 solutions in different languages for Make Array Non-decreasing. Click on different languages to view the code.
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 Solution
Watch the video walkthrough for Make Array Non-decreasing
Similar Questions
5 related questions you might find useful
Patterns:
Data Structures:
Subscribe to Scale Engineer newsletter
Learn about System Design, Software Engineering, and interview experiences every week.
No spam, unsubscribe at any time.