Medium | LeetCode 581. 最短无序连续子数组 | 排序 | 单调栈 | 局部单调性
581. 最短无序连续子数组
给你一个整数数组 nums
,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
请你找出符合题意的 最短 子数组,并输出它的长度。
示例 1:
输入:nums = [2,6,4,8,10,9,15]
输出:5
解释:你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。
示例 2:
输入:nums = [1,2,3,4]
输出:0
示例 3:
输入:nums = [1]
输出:0
提示:
1 <= nums.length <= 104
-105 <= nums[i] <= 105
方法一: 暴力算法
枚举每个子序列[i, j], 枚举此子序列的时间复杂度是O(n^2), 然后找到这个子序列的最小值min和最大值max。如果子序列的前后[0, i-1] 和 [j + 1, n-1], 是升序的, 并且有 num[i-1] < min < max < num[j+1], 则说明这个子数组是一个符合条件的数组, 更新其长度 j-i+1。
所以总的时间复杂度是:O(n^3), 空间复杂度是O(1)
方法二: 暴力遍历
同样是暴力算法, 但是暴力的方法不同。遍历数组元素, 对于每个下标为i的元素nums[i], 从i后边开始遍历一遍数组, 找到比他小的元素nums[j], 如果有这个nums[i-1]小的元素, 那么, 此时的元素[i, j]都不在正确的位置, 并且[i, j]是无序连续数组的边界, 通过双重循环遍历数组的方式得到其子序列的边界。并且在此过程中, 更新边界的值。最终得到的最大的边界值, 就是最短的无序连续子数组。
public int findUnsortedSubarray(int[] nums) {
int l = nums.length, r = 0;
for (int i = 0; i < nums.length - 1; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] < nums[i]) {
r = Math.max(r, j);
l = Math.min(l, i);
}
}
}
return r - l < 0 ? 0 : r - l + 1;
}
这种算法的时间复杂度较第一种好, 其时间复杂度是O(n^2), 空间复杂度是O(1)
方法三: 排序
把数组重新复制一份, 并且排序。然后从两段扫描比较, 从左往右第一个同位置不等的元素就是左边界, 从右往左的第一个同位置不等的元素就是右边界。从而找到答案。
public int findUnsortedSubarray(int[] nums) {
int[] snums = nums.clone();
Arrays.sort(snums);
int start = snums.length, end = 0;
for (int i = 0; i < snums.length; i++) {
if (snums[i] != nums[i]) {
start = Math.min(start, i);
end = Math.max(end, i);
}
}
return (end - start >= 0 ? end - start + 1 : 0);
}
这种算法时间复杂度时排序的时间度, 即O(nlogn), 空间复杂度是O(n), 由于需要复制数组。
方法四:单调栈
借助单调栈, 找到无序的部分的最左边界和最右边界。
从左往右扫描数组, 当前数字比栈顶元素大, 则压栈, 否则, 出栈, 知道比栈顶元素大, 这时, 找到了一个左边界, 同样的方法, 通过不断地将元素出栈, 最后栈里剩下的元素的个数就是其无序数组的左边界。
同样通过从右往左扫描数组可以得到这个数组的右边界。
public int findUnsortedSubarray(int[] nums) {
Stack < Integer > stack = new Stack < Integer > ();
int l = nums.length, r = 0;
for (int i = 0; i < nums.length; i++) {
while (!stack.isEmpty() && nums[stack.peek()] > nums[i])
l = Math.min(l, stack.pop());
stack.push(i);
}
stack.clear();
for (int i = nums.length - 1; i >= 0; i--) {
while (!stack.isEmpty() && nums[stack.peek()] < nums[i])
r = Math.max(r, stack.pop());
stack.push(i);
}
return r - l > 0 ? r - l + 1 : 0;
}
这种时间单调栈找边界的时间复杂度是O(n), 空间复杂度也是O(n)。
方法五
从左往右扫描, 找到处于局部递减的最小元素, 其应当在的位置, 就是无序子数组的左边界。
从右往左扫描, 找到处于局部递增的最大元素, 其应当在的位置, 就是无序子数组的有边界。
public int findUnsortedSubarray(int[] nums) {
int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
for (int i = 1; i < nums.length; i++) {
// 找到一个局部递增的值, 找到局部递减的最小值
if (nums[i] < nums[i-1]) {
min = Math.min(min, nums[i]);
max = Math.max(max, nums[i-1]);
}
}
// 剪枝, 整个数组都是递增的。
if (min > max) {
return 0;
}
int left = 0, right = 0;
for (int i = 0; i < nums.length; i++) {
// 从左往右扫描数组, 找到第一个比最小值大的元素, 这个下标就是左边界
if (nums[i] > min) {
left = i;
break;
}
}
for (int i = nums.length - 1; i >= 0; i--) {
// 从右往左扫描数组, 找到第一个比最大值小的元素, 这个下标就是右边界
if (nums[i] < max) {
right = i;
break;
}
}
return right - left + 1;
}