「力扣」第 581 题:最短无序连续子数组(栈)

247 阅读2分钟

摘要:这道问题关键在于审题,题目只要求我们返回「最短的连续子区间」的长度。还可以借助「栈」的结论,通过出栈元素的下标找到需要修改的左边界最小值和右边界最大值。


「力扣」第 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

提示:

  • 1nums.length1041 \le nums.length \le 10^4
  • 105nums[i]105-10^5 \le nums[i] \le 10^5

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

思路分析

  • 从左到右找比最大值,如果当前位置比最大值小,这个位置就要参与排序,找到 rightBound
  • 从左到右找比最小值,如果当前位置比最小值大,这个位置就要参与排序,找到 leftBound

参考代码 1

public class Solution {

    public int findUnsortedSubarray(int[] nums) {
        int len = nums.length;
        if (len < 2) {
            return 0;
        }

        int maxVal = nums[0];
        int rightBound = 0;
        for (int i = 1; i < len; i++) {
            maxVal = Math.max(maxVal, nums[i]);
            if (nums[i] < maxVal) {
                rightBound = i;
            }
        }

        int leftBound = len - 1;
        int minValue = nums[len - 1];
        for (int i = len - 2; i >= 0; i--) {
            minValue = Math.min(minValue, nums[i]);
            if (nums[i] > minValue) {
                leftBound = i;
            }
        }

        // 区间 [leftBound..rightBound] 需要参与排序
        if (rightBound > leftBound) {
            return rightBound - leftBound + 1;
        }
        return 0;
    }
}

时间复杂度O(N)O(N),这里 NN 是输入输入的长度。

如果熟悉「栈」的应用,就会知道,「栈」可以用于找左边第一个比自己大(或者小)的元素的位置。例如:

[2, 6, 7, 8] 后面来一个 3 ,可以把前面的 876 依次拿掉。

关键:出栈的时候记录下标。

参考代码 2

import java.util.ArrayDeque;
import java.util.Deque;

public class Solution {

    public int findUnsortedSubarray(int[] nums) {
        int len = nums.length;
        if (len < 2) {
            return 0;
        }

        int leftBound = len - 1;
        int rightBound = 0;

        // 重点:栈用于找最近比 nums[i] 严格大的元素的下标
        Deque<Integer> stack = new ArrayDeque<>(len);
        for (int i = 0; i < len; i++) {
            // 这里有点绕:找右边比它严格小的元素
            // 保留的是最左边的下标
            // 这里的 while 不要忘记
            while (!stack.isEmpty() && nums[i] < nums[stack.peekLast()]) {
                leftBound = Math.min(leftBound, stack.removeLast());
            }
            stack.addLast(i);
        }

        stack.clear();
        for (int i = len - 1; i >= 0; i--) {
            while (!stack.isEmpty() && nums[i] > nums[stack.peekLast()]) {
                rightBound = Math.max(rightBound, stack.removeLast());
            }
            stack.addLast(i);
        }

        if (rightBound > leftBound) {
            return rightBound - leftBound + 1;
        }
        return 0;
    }
}

时间复杂度O(N)O(N),这里 NN 是输入输入的长度。所有的元素最多进栈一次、出栈一次。