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

提示:

leet code 581. 最短无序连续子数组_子数组

leet code 581. 最短无序连续子数组_数组_02

进阶: 你可以设计一个时间复杂度为O(n)的解决方案吗?

题目解析

初步思路--超时的解

  • 首先题目仅要求输出最短子数组的长度
  • 那么仅仅知道子数组左右边界的索引位置即可
  • 利用一个辅助栈来维护递增的序列,栈顶元素最大
  • 如果当前元素,小于栈顶元素那么需要进行交换,找到 当前元素应该处于数组对应的哪一个位置
  • 然后这个时候可以记录更新一下 start、end 的位置

show code

class Solution {
     public int findUnsortedSubarray(int[] nums) {
        int n = nums.length;
        if (n == 1) {
            return 0;
        }
        // 用一个栈维护递增序列
        Deque<Integer> stack = new LinkedList<>();
        // 辅助栈,当 遇到元素小于 stack.peek() 的时候辅助交换元素
        Deque<Integer> temporary = new LinkedList<>();
        // start:记录 符合要求的子数组的开始位置
        // end :记录符合要求的子数组的结束位置
        int start = n, end = 0;
        for (int num : nums) {
            // 如果需要进行元素交换排序
            while (!stack.isEmpty() && num < stack.peek()) {
                temporary.push(stack.pop());
            }
            // 记录一次 start 的位置
            if (!temporary.isEmpty()) {
                start = Math.min(start, stack.size());
            }
            stack.push(num);
            // 不断记录更新  end 的位置.
            if (!temporary.isEmpty()) {
                end = stack.size() + temporary.size() - 1;
            }
            while (!temporary.isEmpty()) {
                stack.push(temporary.pop());
            }
        }
        return start == n ? 0 : (end - start + 1);
    }
}
  • 但是超时了
  • 因为两个栈来互相交换元素的过程太过耗时

优化解-O(n)时间复杂度

  • 如果考虑只遍历常数次数组就能够得到最终结果呢?
  • 首先我们仅仅需要得到左右边界
  • 所以,最终应该要求得 “最短无序连续子数组” 中最大值和最小值对应的索引位置
  • 那么这个 “最短无序连续子数组” 具有什么性质呢?
  1. 其位于原数组的中间
  2. 其最小值 大于 其左段的最大值
  3. 其最大值 小于 其右段的最小值
  1. 那么从左到右维护一个最大值 max,那么在进入右段之前,所有的值都小于 max
  2. 那么右边界end对应的就是最后一个小于max的元素
  3. 同理,从右到左维护一个最小值 min,在进入左段之前,所有的值都大于 min
  4. 那么左边界begin就是最后一个大于 min 元素的位置

show code

class Solution {
    public int findUnsortedSubarray(int[] nums) {
        int n = nums.length;
        // 初始化一个最大值和一个最小值
        int min = Integer.MAX_VALUE;
        int max = Integer.MIN_VALUE;
        // 初始化 begin end 对应 最短连续子数组 的左右边界
        int begin = 0,end = -1;
        // 开始遍历
        for(int i = 0;i < n;i++) {
            // 所有的 nums[i] 小于 max
            // 找到最后一个小于 max 位置的索引
            if(nums[i] < max) {
                end = i;
            } else {
                max = nums[i];
            }

            // 所有的 nums[i] 大于min
            // 寻找最后一个大于 min 位置的索引
            if(nums[n - i - 1] > min) {
                begin = n - i - 1;
            } else {
                min = nums[n - i - 1];
            }
        }
        return end - begin + 1;
    }
}