题目描述
给你一个整数数组 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
解题思路
排序+双指针
- 将数组 nums 进行排序,记为 numsSort,然后比较 nums 和 numsSort 的元素来决定最左边和最右边不匹配的元素。它们之间的子数组就是要求的最短无序子数组。
- 首先,从左向右遍历两个数组,找到第一次两个数组中元素不同的位置,即为最左边的不同的位置也就是最短无序连续子数组的左边界。
- 然后,从右向左遍历两个数组,找到第一次两个数组中元素不同的位置,即为最右边的不同的位置也就是最短无序连续子数组的右边界。
/**
* 排序 双指针
*
* 将数组 nums 进行排序,记为 numsSort,然后比较 nums 和 numsSort 的元素来决定最左边和最右边不匹配的元素。它们之间的子数组就是要求的最短无序子数组。
*
* 首先,从左向右遍历两个数组,找到第一次两个数组中元素不同的位置,即为最左边的不同的位置也就是最短无序连续子数组的左边界。
*
* 然后,从右向左遍历两个数组,找到第一次两个数组中元素不同的位置,即为最右边的不同的位置也就是最短无序连续子数组的右边界。
* @param nums
* @return
*/
public int findUnsortedSubarray_1(int[] nums) {
int n = nums.length;
if (n == 1)
return 0;
int[] numSort = nums.clone();
Arrays.sort(numSort);
int left = Integer.MAX_VALUE,right = 0;
for (int i = 0; i < n; i++) {
if (numSort[i] != nums[i]){
left = i;
break;
}
}
for (int i = n - 1; i >= 0;i--){
if (numSort[i] != nums[i]){
right = i;
break;
}
}
if (right - left >= 0)
return right - left + 1;
else
return 0;
}
单调栈
我们需要找到无序子数组中最小元素和最大元素分别对应的正确位置,来求得我们想要的无序子数组的边界。
-
为了达到这一目的,此方法中,我们使用 栈 。我们从头遍历 nums 数组,如果遇到的数字大小一直是升序的,我们就不断把对应的下标压入栈中,这么做的目的是因为这些元素在目前都是处于正确的位置上。一旦我们遇到前面的数比后面的数大,也就是 nums[j] 比栈顶元素小,我们可以知道 nums[j] 一定不在正确的位置上。
-
为了找到 nums[j]的正确位置,我们不断将栈顶元素弹出,直到栈顶元素比 nums[j]小,我们假设栈顶元素对应的下标为 k ,那么我们知道 nums[j] 的正确位置下标应该是k+1 。
-
我们重复这一过程并遍历完整个数组,这样我们可以找到最小的 k, 它也是无序子数组的左边界。
时间复杂度:O(n)。需要遍历数组一遍,栈的时间复杂度也为 O(n)。
空间复杂度:O(n)。栈的大小最大达到 n。
public int findUnsortedSubarray_4(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;
}
最优答案:
背后的思想是无序子数组中最小元素的正确位置可以决定左边界,最大元素的正确位置可以决定右边界。
因此,首先我们需要找到原数组在哪个位置开始不是升序的。我们从头开始遍历数组,一旦遇到降序的元素,我们记录最小元素为 min 。
类似的,我们逆序扫描数组 nums,当数组出现升序的时候,我们记录最大元素为 max。
然后,我们再次遍历 nums 数组并通过与其他元素进行比较,来找到 min 和 max 在原数组中的正确位置。我们只需要从头开始找到第一个大于min 的元素,从尾开始找到第一个小于 max 的元素,它们之间就是最短无序子数组。
public class Solution {
public int findUnsortedSubarray(int[] nums) {
int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
boolean flag = false;
for (int i = 1; i < nums.length; i++) {
if (nums[i] < nums[i - 1])
flag = true;
if (flag)
min = Math.min(min, nums[i]);
}
flag = false;
for (int i = nums.length - 2; i >= 0; i--) {
if (nums[i] > nums[i + 1])
flag = true;
if (flag)
max = Math.max(max, nums[i]);
}
int l, r;
for (l = 0; l < nums.length; l++) {
if (min < nums[l])
break;
}
for (r = nums.length - 1; r >= 0; r--) {
if (max > nums[r])
break;
}
return r - l < 0 ? 0 : r - l + 1;
}
}