LeetCode.581 每日一题| 8月更文挑战

159 阅读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

提示:

  • 1 <= nums.length <= 104
  • -105 <= nums[i] <= 105

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

方法一

排序:根据题意,我们可以将数组分成三段、A、B、C,其中A和C排序前后是相同的;要找到最短的B,即我们遍历数组找到最靠右边的A边界和最靠左边的C边界;

class Solution {
    public int findUnsortedSubarray(int[] nums) {
        int n = nums.length;
        int[] sortedNums = new int[n];
        for (int i = 0; i < n; i ++ ) sortedNums[i] = nums[i];
        Arrays.sort(sortedNums);
        
        int x = 0, y = n - 1;
        while(x < n && nums[x] == sortedNums[x]) x ++;
        while(y > x && nums[y] == sortedNums[y]) y --;
        if (x == y) return 0;
        return y - x + 1;
    }
}

时间复杂度: O(nlogn)

空间复杂度: O(n)

方法二

扫描:整体思路和题解1其实是类似的,找到两段已经排好序的长度。我们先使用一遍扫描找到左边保持升序的最后一个点的位置left,和从右向左看保持降序的最后一个点的位置right。如果已经这时候left == right说明已经排好序了,无需调整。接下来我们从left + 1的位置向右扫描,如果遇到有比nums[left]小的元素,说明最起码left不在正确位置上,那么left --。同样的,我们从right - 1的位置向左扫描,如果遇到有比nums[right]大的元素,说明最起码nums[right]不在正确的位置上,right ++。最后,leftright之间的元素就是需要重新排序的元素,长度为right - left - 1

class Solution {
    public int findUnsortedSubarray(int[] nums) {
        int n = nums.length;
        if (n == 1) return 0;
​
        int left = 0, right = n - 1;
        while(left < n - 1 && nums[left] <= nums[left + 1]) left ++;
        while(nums[right] >= nums[right - 1] && right > left) right --;
        if (right == left) return 0;
​
        for (int i = left + 1; i < n; i ++ )
            while(left >= 0 && nums[i] < nums[left])
                left --;
        
        for (int i = right - 1; i >= 0; i --) 
            while(right < n && nums[i] > nums[right])
                right ++;
        return right - left - 1;
    }
}

时间复杂度: O(n)

空间复杂度: O(1)