LeetCode 915. 分割数组
题目描述


题意分析
在数组中选一个切分位置,左段和右段都必须非空,而且左段任意元素都不能大于右段任意元素。两段内部不要求有序,也不能先排序改变原数组的位置关系。题目保证至少存在一种合法划分,返回其中最短的左段长度。
解法:后缀最小值 + 滚动前缀最大值
核心思路
[!blue]
设右段从下标
k开始,左段就是nums[0..k-1],长度恰好为k。如果左段最大值不大于右段最小值,那么左段中的其他值只会更小,右段中的其他值只会更大,所有跨段比较都满足要求;反之,这两个极值不满足时就已经构成一个反例。因此只需比较两段极值。定义
suffixMin[i]为nums[i..n-1]的最小值。从右向左计算suffixMin[i] = min(nums[i], suffixMin[i+1]),便能直接取得每个切分位置的右段最小值。再从左向右枚举
k,用prefixMax保存nums[0..k-1]的最大值。每轮先检查prefixMax <= suffixMin[k],失败后才把nums[k]纳入左段,为下一个边界准备状态。所有更小的边界已经被排除,所以第一个通过检查的k就是最短左段长度。
解题步骤
- 令
suffixMin[n-1] = nums[n-1],从n-2到 0 依次计算后缀最小值。- 令
prefixMax = nums[0],从k=1开始枚举,保证左段非空。- 若
prefixMax <= suffixMin[k],立即返回k。- 否则更新
prefixMax = max(prefixMax, nums[k]),继续检查下一边界;枚举只到n-1,保证右段非空。所有元素相等时,第一个边界就合法,答案为 1,因此比较必须允许相等。题目保证有解,正常输入一定会在循环中返回,末尾的
-1不会被执行。
代码实现
class Solution {
public int partitionDisjoint(int[] nums) {
int n = nums.length;
int[] suffixMin = new int[n];
suffixMin[n - 1] = nums[n - 1];
for (int i = n - 2; i >= 0; i--) {
suffixMin[i] = Math.min(nums[i], suffixMin[i + 1]);
}
int prefixMax = nums[0];
for (int k = 1; k < n; k++) {
// 先比较当前边界,再把右段首项纳入下一轮前缀
if (prefixMax <= suffixMin[k]) {
return k;
}
prefixMax = Math.max(prefixMax, nums[k]);
}
return -1;
}
}
func partitionDisjoint(nums []int) int {
n := len(nums)
suffixMin := make([]int, n)
suffixMin[n-1] = nums[n-1]
for i := n - 2; i >= 0; i-- {
if nums[i] < suffixMin[i+1] {
suffixMin[i] = nums[i]
} else {
suffixMin[i] = suffixMin[i+1]
}
}
prefixMax := nums[0]
for k := 1; k < n; k++ {
// 先比较当前边界,再把右段首项纳入下一轮前缀
if prefixMax <= suffixMin[k] {
return k
}
if nums[k] > prefixMax {
prefixMax = nums[k]
}
}
return -1
}
复杂度分析
- 时间复杂度:$O(n)$,反向预处理和正向扫描。
- 空间复杂度:$O(n)$,保存后缀最小值。
关键点总结
[!green]
- 边界下标恰好也是左段长度。
- 后缀最小值预处理一次即可复用,前缀最大值只依赖上一轮,使用一个变量即可。
- 比较时两段必须恰好覆盖数组且互不重叠,
nums[k]此时仍属于右段。
易错点总结
[!yellow]
- 只比较边界相邻两项,不能代表整段极值。
- 比较前纳入当前项,会把右段首项误算到左段。
- 允许边界零或长度,产生空段。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 769. 最多能完成排序的块 | 中等 | 同样判断左右值域能否独立排序,本题只取一个最早分割,原题求最多分块。 |
| 581. 最短无序连续子数组 | 中等 | 同样通过前缀最大值与后缀最小值定位跨边界无序,本题找安全切点,原题找必须排序的区间。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!