LeetCode 581. 最短无序连续子数组
题目描述


题意分析
选择一个尽量短的连续区间,只将这段元素按非递减顺序排序,就能使整个数组非递减,返回该区间的长度。区间外的元素不移动;数组原本有序时返回零。
非递减允许相邻元素相等。无序影响不只发生在相邻位置,一个偏大的值可能需要跨过多个元素才能归位,所以需要找到全部逆序关系必须覆盖的最左、最右位置。
解法:双向扫描确定左右边界
核心思路
[!blue]
若存在
i < j且nums[i] > nums[j],就有一对逆序。要通过排序一段连续区间消除它,这两个位置都必须被包含:若将右端位置留在区间外,左边那个更大的元素仍留在它前面的某处;将左端位置留在区间外时也同理。从左到右扫描,用
maxSeen保存此前元素的最大值。若nums[i] < maxSeen,当前位置就是某对逆序的右端,待排序区间必须延伸到这里,因此更新right = i。持续覆盖后,right是所有逆序右端中最靠右的位置。从右到左同理,用
minSeen保存后面元素的最小值。若nums[i] > minSeen,当前位置是逆序左端,就更新left = i,最终得到最靠左的必要位置。相等不构成逆序,不能使用非严格比较。得到的
[left, right]不仅必要,也足够:任何逆序的两端都在这个区间内,区间外本身有序,也不存在跨过左右边界的逆序。将内部排序后,内部逆序消失,区间内外的数值集合不变,因此边界也仍满足顺序,整个数组就有序了。所有可行区间都必须覆盖这两个极端位置,而排序它们之间的区间已经足够,所以长度最短。用
right = -1表示尚未发现逆序;若扫描后仍为这个值,直接返回零,否则返回right - left + 1。
解题步骤
- 初始化
right = -1、maxSeen为整数最小值,从左到右扫描;当前值更小时更新右边界,否则更新前缀最大值。- 初始化
left = n、minSeen为整数最大值,从右到左扫描;当前值更大时更新左边界,否则更新后缀最小值。- 若
right == -1,说明数组已经非递减,返回零。- 否则返回闭区间长度
right - left + 1,无需实际排序或修改数组。
代码实现
class Solution {
public int findUnsortedSubarray(int[] nums) {
int n = nums.length;
int right = -1;
int maxSeen = Integer.MIN_VALUE;
for (int i = 0; i < n; i++) {
if (nums[i] < maxSeen) {
// 当前值小于左侧最大值,说明它必须进入待排序区间。
right = i;
} else {
maxSeen = nums[i];
}
}
int left = n;
int minSeen = Integer.MAX_VALUE;
for (int i = n - 1; i >= 0; i--) {
if (nums[i] > minSeen) {
// 当前位置大于后缀最小值,待排序区间必须覆盖这个逆序端点。
left = i;
} else {
minSeen = nums[i];
}
}
if (right == -1) {
return 0;
}
return right - left + 1;
}
}
func findUnsortedSubarray(nums []int) int {
n := len(nums)
minInt := -int(^uint(0)>>1) - 1
maxInt := int(^uint(0) >> 1)
right := -1
maxSeen := minInt
for i := 0; i < n; i++ {
if nums[i] < maxSeen {
// 当前值小于左侧最大值,说明它必须进入待排序区间。
right = i
} else {
maxSeen = nums[i]
}
}
left := n
minSeen := maxInt
for i := n - 1; i >= 0; i-- {
if nums[i] > minSeen {
// 当前位置大于后缀最小值,待排序区间必须覆盖这个逆序端点。
left = i
} else {
minSeen = nums[i]
}
}
if right == -1 {
return 0
}
return right - left + 1
}
复杂度分析
- 时间复杂度:$O(n)$,两次线性扫描。
- 空间复杂度:$O(1)$,只使用常数个变量。
关键点总结
[!green]
- 右边界看前缀最大值,左边界看后缀最小值,两次扫描缺一不可。
- 相等不算逆序,判断必须是
<和>。- 用
right = -1同时表达空区间和数组已有序,无需额外检查。
易错点总结
[!yellow]
- 只检查相邻逆序会忽略较大值与远处较小值的关系,边界可能收得过窄。
- 使用
<=或>=会把允许存在的重复值误判为无序,应采用严格比较。- 前缀最大值和后缀最小值不能都从零开始,否则负数或正数输入可能产生虚假的逆序。
- 已有序时要先返回零,不能用未初始化成有效区间的左右边界计算长度。
- 返回的是闭区间包含的元素数,因此两端下标相减后还要加一。
相似题目
| 题目 | 难度 | 关联与区别 |
|---|---|---|
| 915. 分割数组 | 中等 | 同样利用左侧最大值与右侧最小值判定是否有跨边界逆序,本题要同时确定最短待排序区间的两端。 |
| 769. 最多能完成排序的块 | 中等 | 同样分析哪些区间能独立排序,本题只允许排序一个连续段,原题尽可能划分多个块。 |
转载与许可
许可
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!