题目描述
这个问题和“最多能完成排序的块”相似,但给定数组中的元素可以重复,输入数组最大长度为2000,其中的元素最大为108。
arr是一个可能包含重复元素的整数数组,我们将这个数组分割成几个“块”,并将这些块分别进行排序。之后再连接起来,使得连接的结果和按升序排序后的原数组相同。
我们最多能将数组分成多少块?
示例 1:
输入: arr = [5,4,3,2,1]
输出: 1
解释:
将数组分成2块或者更多块,都无法得到所需的结果。
例如,分成 [5, 4], [3, 2, 1] 的结果是 [4, 5, 1, 2, 3],这不是有序的数组。
示例 2:
输入: arr = [2,1,3,4,4]
输出: 4
解释:
我们可以把它分成两块,例如 [2, 1], [3, 4, 4]。
然而,分成 [2, 1], [3], [4], [4] 可以得到最多的块数。
题解
题解1:辅助栈
找到可以完成排序的块,实际上就是在找局部最大值,且局部最大值按升序顺序排列。
局部最大值有多少个,则可以完成排序的块就有多少个,每个局部最大值对应一个块。
使用辅助栈对局部最大值进行统计。
- 首先将数组首元素当作第一个最大值,入栈;
- 若当前值大于等于栈顶元素,则将当前值入栈,相当于增加了一个局部最大值;
- 若当前值小于栈顶元素(说明需要将此元素合并到前一个块中,也就是需要减少栈内的局部最大值,)首先保存最大的栈顶元素,之后弹出,继续对比栈内元素,若小于栈内元素,继续弹出,直到当前值大于等于栈顶元素,或栈为空,此时将保存的最大值进栈,合并完成。
最终块的个数即为局部最大值的个数。
class Solution {
public:
int maxChunksToSorted(vector<int>& arr) {
stack<int> stk;
for(int i = 0; i < arr.size(); ++i){
if(stk.empty() || arr[i] >= stk.top()) stk.push(arr[i]);
else{
int head = stk.top();//排序块的最大值
stk.pop();
//需要和前面的排序块合并
while(!stk.empty() && arr[i] < stk.top()) stk.pop();
stk.push(head);//插入排序块的最大值
}
}
return stk.size();
}
};
复杂度分析
时间复杂度: O(N),其中 N 为数组长度。
空间复杂度: O(N)。
题解2:求和法
参考: 768. 最多能完成排序的块 II【滑动窗口、单调栈、JavaScript】
原数组进行分块后,每一个分块排序后和排序后的数组中对应的数字是一样的,因此它们的和也是一样的了。我们可以用一个滑动窗口同时扫描原数组和排序数组,当窗口中数字的和一样时,就将数组进行分块。
class Solution {
public:
int maxChunksToSorted(vector<int>& arr) {
vector<int> sorted_arr = arr;
int size = arr.size();
sort(sorted_arr.begin(),sorted_arr.end());
int count = 0;
long int sum1 = 0;
long int sum2 = 0;
for(int i = 0; i < size; i++){
cout << "sorted_arr[ " << i << "] ="<< sorted_arr[i] << endl;
sum1 += arr[i];
sum2 += sorted_arr[i];
if(sum1 == sum2){
count++;
}
}
return count;
}
};
复杂度分析
- 时间复杂度:O(NlogN),N 为数组长度,数组排序时间认为是 NlogN,滑动窗口遍历数组时间为 N。
- 空间复杂度:O(N),N 为数组长度。
参考
最多能完成排序的块 II (辅助栈法,清晰图解)
768. 最多能完成排序的块 II 滑动窗口、单调栈、JavaScript
C++ 左右遍历极小极大
C++,利用栈,栈头保留当前排序块的最大值
单调栈——最多能完成排序的块2