LeetCode 768. 最多能完成排序的块 II
题目描述

题意分析
将数组划分为尽可能多的连续块,分别排序后连接起来与整个数组排序结果相同;元素可以重复。
解法:单调栈合并块
核心思路
[!blue]
两块能分开排序后直接拼接,要求前块的所有元素都不大于后块的所有元素;若存在跨越边界的逆序,块内排序无法消除它,这条边界就不能保留。用栈保存已扫描前缀各块的最大值,并维护各块之间已经满足上述顺序,因此栈内最大值非递减。
读入新值
x时,若它不小于栈顶最大值,之前所有元素都不大于x,可以将它单独作为一个新块。相等也允许单独成块,因为最终只要求非递减顺序。若
x小于栈顶,最后一块中存在比它大的元素,这一块必须与x合并。先弹出该块并保存它的最大值curMax,再继续弹出所有最大值大于x的后缀块,更新合并后的最大值。停止条件始终与当前的x比较,最后压回curMax,保留整块真实的最大元素。停止时,剩余栈顶最大值不超过
x,也不超过被合并旧块里的任何元素,所以它与合并块之间可以保留边界。被弹出的边界则都被“前面某个数大于当前x”的逆序跨过,任何合法划分都必须去掉它们。算法始终保留所有仍合法的边界,只删除被逆序强制取消的边界;已经失效的边界也不会因为后续元素加入而重新有效。因此最终不仅能正确排序,而且保留的块数最多,答案就是栈大小。
解题步骤
- 依次读取数组元素。
- 新值不小于栈顶时,将其作为新块最大值入栈。
- 新值小于栈顶时,弹出所有最大值超过它的后缀块。
- 将合并后的最大值重新入栈,最终栈大小就是最多块数。
代码实现
class Solution {
public int maxChunksToSorted(int[] arr) {
Deque<Integer> st = new ArrayDeque<>();
for (int x : arr) {
if (st.isEmpty() || x >= st.peekLast()) {
st.addLast(x);
continue;
}
// 保存待合并后缀的最大值,当前较小元素不能替代它。
int curMax = st.pollLast();
// 所有与当前值形成跨块逆序的后缀块都必须合并。
while (!st.isEmpty() && st.peekLast() > x) {
curMax = Math.max(curMax, st.pollLast());
}
st.addLast(curMax);
}
return st.size();
}
}
func maxChunksToSorted(arr []int) int {
st := make([]int, 0, len(arr))
for _, x := range arr {
if len(st) == 0 || x >= st[len(st)-1] {
st = append(st, x)
continue
}
// 保存待合并后缀的最大值,当前较小元素不能替代它。
curMax := st[len(st)-1]
st = st[:len(st)-1]
// 所有与当前值形成跨块逆序的后缀块都必须合并。
for len(st) > 0 && st[len(st)-1] > x {
if st[len(st)-1] > curMax {
curMax = st[len(st)-1]
}
st = st[:len(st)-1]
}
st = append(st, curMax)
}
return len(st)
}
复杂度分析
- 时间复杂度:$O(n)$,
n为数组长度,每轮只入栈一次,每个栈条目至多弹出一次,合并循环总次数为线性。- 空间复杂度:$O(n)$,数组已有序时每个元素都能独立成块,栈最多保存
n个最大值。
关键点总结
[!green]
- 栈保存块最大值,不是完整块内容。
- 合并只影响栈尾连续几块,前面已经满足顺序的块可以保留。
- 相等值不会造成跨块逆序,因此可以分开。
易错点总结
[!yellow]
- 发现逆序只弹出一块:当前值可能比多个前块的最大值都小。
- 合并后只压入当前较小值:丢失合并块真实最大值。
- 使用大于而非大于等于建立新块:会把可以拆开的相等值合并。
- 将栈高度理解为最长递增子序列长度:这里统计的是满足整体排序约束的连续分块。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 769. 最多能完成排序的块 | 中等 | 原题输入为0到n-1的排列,可用前缀最大值等于下标判切点,本题含一般数值和重复。 |
| 581. 最短无序连续子数组 | 中等 | 同样通过跨边界逆序判断排序影响范围,本题尽量切成多个可独立排序的块。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!