Loading

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;
}
posted @ 2021-01-25 23:07  反身而诚、  阅读(74)  评论(0编辑  收藏  举报