一、题目
给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
你找到的子数组应是最短的,请输出它的长度。
示例 1:
输入: [2, 6, 4, 8, 10, 9, 15]
输出: 5
解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。
说明 :
输入的数组长度范围在 [1, 10,000]。
输入的数组可能包含重复元素 ,所以升序的意思是<=。
二、思路及代码
思路一:
拷贝一份数组并进行排序,然后比较两个数组,找到最左边和最右边不匹配的元素,它们之间的子数组就是要求的最短无序子数组。
代码如下:
class Solution {
public int findUnsortedSubarray(int[] nums) {
int[] sortNums = Arrays.copyOf(nums, nums.length);
Arrays.sort(sortNums);
int left = 0;
int right = 0;
for(int i = 0; i < nums.length; i++){
if(nums[i] != sortNums[i]){
left = i;
break;
}
}
for(int j = nums.length - 1; j >= 0; j--){
if(nums[j] != sortNums[j]){
right = j;
break;
}
}
// right - left == 0 说明数组是有序的
return right - left > 0 ? right - left + 1 : 0;
}
}
思路二:单调栈(官方题解)
这个方法背后的想法仍然是选择排序。我们需要找到无序子数组中最小元素和最大元素分别对应的正确位置,来求得我们想要的无序子数组的边界。
为了达到这一目的,此方法中,我们使用栈 。我们从头遍历 nums 数组,如果遇到的数字大小一直是升序的,我们就不断把对应的下标压入栈中,这么做的目的是因为这些元素在目前都是处于正确的位置上。一旦我们遇到前面的数比后面的数大,也就是 nums[j] 比栈顶元素小,我们可以知道 nums[j] 一定不在正确的位置上。
为了找到 nums[j] 的正确位置,我们不断将栈顶元素弹出,直到栈顶元素比 nums[j] 小,我们假设栈顶元素对应的下标为 k ,那么我们知道 nums[j] 的正确位置下标应该是 k + 1 。
我们重复这一过程并遍历完整个数组,这样我们可以找到最小的 k, 它也是无序子数组的左边界。
类似的,我们逆序遍历一遍 nums 数组来找到无序子数组的右边界。这一次我们将降序的元素压入栈中,如果遇到一个升序的元素,我们像上面所述的方法一样不断将栈顶元素弹出,直到找到一个更大的元素,以此找到无序子数组的右边界。
class Solution {
public int findUnsortedSubarray(int[] nums) {
LinkedList<Integer> stack = new LinkedList<>();
int left = nums.length - 1;
int right = 0;
for(int i = 0; i < nums.length; i++){
while(!stack.isEmpty() && nums[stack.peek()] > nums[i]){
left = Math.min(left, stack.pop());
}
stack.push(i);
}
stack.clear();
for(int j = nums.length - 1; j >= 0; j--){
while(!stack.isEmpty() && nums[stack.peek()] < nums[j]){
right = Math.max(right, stack.pop());
}
stack.push(j);
}
return right - left > 0 ? right - left + 1 : 0;
}
}