Medium
题目描述
给你一个整数数组 nums。在一次操作中,你可以选择一个子数组,并将其替换为一个等于该子数组最大值的单个元素。
返回执行零次或多次操作后,使结果数组非递减的数组的最大可能大小。
示例 1:
输入:nums = [4,2,5,3,5]
输出:3
解释:
实现最大大小的一种方法是:
- 将子数组 nums[1..2] = [2, 5] 替换为 5 → [4, 5, 3, 5]
- 将子数组 nums[2..3] = [3, 5] 替换为 5 → [4, 5, 5]
最终数组 [4, 5, 5] 是非递减的,大小为 3。
示例 2:
输入:nums = [1,2,3]
输出:3
解释:
不需要任何操作,因为数组 [1,2,3] 已经是非递减的。
约束条件:
1 <= nums.length <= 2 * 10^51 <= nums[i] <= 2 * 10^5
提示:
- 从后向前遍历。
- 你能移除数组中的最大元素吗?这是否有帮助?
解题思路
这是一个贪心算法问题,需要从右向左遍历数组来构建最优解。
核心思路:
我们从右向左遍历数组,维护一个单调递增的栈。当遇到一个元素时,我们需要决定是否将其与右边的某些元素合并。
算法步骤:
- 从右向左遍历数组,使用栈来维护当前的非递减序列
- 对于每个元素
nums[i],我们需要检查它是否能够直接加入栈中 - 如果
nums[i]大于栈顶元素,说明违反了非递减性质,我们需要将栈中所有小于nums[i]的元素弹出 - 被弹出的元素实际上被"合并"到了当前元素中,因为我们可以选择一个包含这些元素的子数组,将其替换为最大值
关键观察:
- 当我们从右向左处理时,如果当前元素比右边已确定的元素大,那么我们可以选择将它们合并
- 合并操作总是选择子数组的最大值,这样可以最大化保留元素的数量
- 使用栈可以高效地维护当前的非递减序列
这个贪心策略是最优的,因为我们总是尽可能保留更多的元素,只有在必要时才进行合并。
代码实现
class Solution {
public:
int maximumPossibleSize(vector<int>& nums) {
vector<int> stack;
for (int i = nums.size() - 1; i >= 0; i--) {
while (!stack.empty() && stack.back() < nums[i]) {
stack.pop_back();
}
stack.push_back(nums[i]);
}
return stack.size();
}
};
class Solution:
def maximumPossibleSize(self, nums: List[int]) -> int:
stack = []
for i in range(len(nums) - 1, -1, -1):
while stack and stack[-1] < nums[i]:
stack.pop()
stack.append(nums[i])
return len(stack)
public class Solution {
public int MaximumPossibleSize(int[] nums) {
List<int> stack = new List<int>();
for (int i = nums.Length - 1; i >= 0; i--) {
while (stack.Count > 0 && stack[stack.Count - 1] < nums[i]) {
stack.RemoveAt(stack.Count - 1);
}
stack.Add(nums[i]);
}
return stack.Count;
}
}
var maximumPossibleSize = function(nums) {
const stack = [];
for (let i = nums.length - 1; i >= 0; i--) {
while (stack.length > 0 && stack[stack.length - 1] < nums[i]) {
stack.pop();
}
stack.push(nums[i]);
}
return stack.length;
};
复杂度分析
| 复杂度类型 | 复杂度 | 说明 |
|---|---|---|
| 时间复杂度 | O(n) | 每个元素最多被压入和弹出栈一次 |
| 空间复杂度 | O(n) | 栈的最大大小为n |