思路
可以发现相邻两个块具有如下性质:前面一个块的「最大值」一定小于后一个块的「最小值」。
亲测,后面两个方法能通过768题。768. 最多能完成排序的块 II。
贪心(占位置)
该方法只适用于数组元素值为
0
到n-1
的情况。如果数组内元素值可以相同,且不在这个范围内,则该方法失效,如题:768. 最多能完成排序的块 II。可使用后面两种方法。
由题意可知,长度为n
的数组内元素值的是[0,n-1]
范围内的整数的一个排列。那么显然,该数组arr
排序后必有arr[i] = i
。
因此,我们可以进行下述的思考,遍历到下标i
的元素arr[i]
时,若有:
arr[i] < i
,则说明这个元素应该放在前面,显然下标i
之前的元素排序后arr[i]
能够放在正确的位置上;arr[i] == i
,则说明该元素已经在正确的位置上了;arr[i] > i
,说明这个元素应该放在后面,下标i
之前的元素排序后arr[i]
仍无法放置在正确的位置上,至少需要遍历到下标i = arr[i]
处。
可以利用一个变量righ
表示:当前块区间右边界下标至少为right
。
当遍历到下标i==right
时,该块内元素排序后一定等价于原数组排序后该块区间内的值的。
代码如下:
class Solution {
public:
int maxChunksToSorted(vector<int>& arr) {
auto n = arr.size();
int ans = 0;
int right = 0; //表示当前块的右边界
for (int i = 0; i < n; ++i) {
//对于arr[i],它真实的位置应该在 idx = arr[i] 上。
right = max(right, arr[i]);
if (i == right){
ans++;
right++;
}
}
return ans;
}
};
模拟
由上一节分析可知,贪心的方法只适用于数组元素值为0
到n-1
的情况。
当数组内元素可重复且元素组任意时如何解决呢?
考虑块已经分好的情况,相邻的两个块chunk[x]
以及chunk[x+1]
一定是满足以下条件的:
max { c h u n k [ x ] } < m i n { c h u n k [ x + 1 ] } \max \{ chunk[x] \} < min \{ chunk[x+1] \} max{chunk[x]}<min{chunk[x+1]}
那么在块还没有确定下来的情况时,应该如何处理?假设前面的块已经确定了,此时当前块chunk[x]
的左边界下标为curIdx
,但是右边界nextIdx-1
还没有确定(nextIdx
为下一个块的起始位置)。下面对如何确定nextIdx
进行分析:
- 初始化时,可以令
nextIdx = curIdx+1
,此时chunk[x]
是一个长度为1
的块。 - 初始化结束后,我们需要去寻找这样一个下标
nextIdx
:区间[nextIdx, n-1]
内的元素最小值是大于区间[curIdx, nextIdx-1]
内元素的最大值的。显然,我们需要对区间[curIdx, n-1]
进行一次遍历。 - 遍历到下标
j
时,可能有以下情况:arr[j] < max{chunk[x]}
,显然arr[j]
也是属于块chunk[x]
内的,因此区间右边界可以进行拓展:nextIdx = j+1
。arr[j] >= max{chunk[x]}
,此时arr[j]
可能是属于下一个块的,因此不进行扩展。
注意到,这里每次扩展后
max{chunk[x]}
都可能发生改变,如果每次去遍历区间[curIdx, nextIdx-1]
去得到最大值会十分费时。可以利用两个变量进行记录:
curMaxValue
表示区间[curIdx, nextIdx-1]
,即当前块内的最大值;maxValue
表示区间[curIdx, j]
内的最大值。显然
maxValue >= curMaxValue
,一旦chunk[x]
区间进行了扩展,令curMaxValue = maxValue
即可。
- 遍历结束后,当前块
chunk[x]
的右边界就确定了下来,块数加1,令curIdx = nextIdx
进行下一轮循环即可。
代码如下:
class Solution {
public:
int maxChunksToSorted(vector<int>& arr) {
auto n = arr.size();
int ans = 0;
int curIdx = 0, nextIdx, j;
int maxValue; //已遍历区间内的最大值
int curMaxValue; // 当前块的最大值
while(curIdx < n){
nextIdx = curIdx + 1;
j = nextIdx; //arr[curIdx...nextIdx-1]为当前块
curMaxValue = maxValue = arr[curIdx];
while(j<n){ //向右拓展边界
if (arr[j] < curMaxValue) {
nextIdx = j+1;
curMaxValue = maxValue;
}
maxValue = max(maxValue, arr[j])
j++;
}
ans++;
curIdx = nextIdx;
}
return ans;
}
};
时间复杂度为 O ( n 2 ) O(n^2) O(n2)
单调栈
是否存在时间复杂度为 O ( n ) O(n) O(n)的方法?可以利用单调栈来实现。
对于arr[0]...arr[i]
,假设其分块方式为chunk[0],chunk[1],...,chunk[x]
。此时新加入一个数arr[i+1]
,若有:
arr[i+1] >= max{chunk[x]}
,则说明arr[i+1]
可以自成一个块chunk[x+1]
;arr[i+1] < max{chunk[x]}
,则arr[i+1]
应合并进入chunk[x]
,合并后还没有结束,还需要继续向前进行判断,直至arr[i+1] >= max{chunk[y]}
,此时chunk[y],chunk[y+1],...,chunk[x], arr[i+1]
合并为新的chunk[y+1]
,并且此时有max{chunk[y+1]} = max{chunk[x]}
。
每次遍历到一个新的元素都是需要从后往前判断,并将合并后的结果加在最后,与栈的操作相同。可以利用单调栈来实现,栈中元素为每个块的最大值。
遍历结束后,栈中元素的数量即为答案。
代码如下:
class Solution {
public:
int maxChunksToSorted(vector<int>& arr) {
auto n = arr.size();
int stk[n];
int top = -1; //栈顶下标
for (const auto &item : arr){
if (top < 0 || item >= stk[top]){
stk[++top] = item;
} else {
int maxValue = stk[top];
while (top >= 0 && stk[top] > item){
top--;
}
stk[++top] = maxValue;
}
}
return top+1;
}
};