zoukankan      html  css  js  c++  java
  • [LeetCode] 581. Shortest Unsorted Continuous Subarray

    Given an integer array nums, you need to find one continuous subarray that if you only sort this subarray in ascending order, then the whole array will be sorted in ascending order.

    Return the shortest such subarray and output its length.

    Example 1:

    Input: nums = [2,6,4,8,10,9,15]
    Output: 5
    Explanation: You need to sort [6, 4, 8, 10, 9] in ascending order to make the whole array sorted in ascending order.
    

    Example 2:

    Input: nums = [1,2,3,4]
    Output: 0
    

    Example 3:

    Input: nums = [1]
    Output: 0

    Constraints:

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

    Follow up: Can you solve it in O(n) time complexity?

    最短无序连续子数组。

    给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。

    请你找出符合题意的 最短 子数组,并输出它的长度。

    来源:力扣(LeetCode)
    链接:https://leetcode-cn.com/problems/shortest-unsorted-continuous-subarray
    著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

    最优解是双指针。我参考了这个帖子。这个做法不需要排序,只需要对 input 数组扫描一遍即可。既然是尽量翻转一个较短的子数组,那么如果用左右两个指针往中间逼近的时候,左指针一边(把指针想象成一个隔断,再某两个数字之间隔开,右指针同理),靠外的数字较小,靠中间的数字较大;右指针靠外的部分较大,靠中间的部分较小。比如左半边一直是 nums[l] < nums[l + 1],右半边一直是 nums[r] > nums[r + 1]。如果数组满足这个条件,那么需要被翻转的部分就会一点点被缩小;一旦不满足这个条件的时候,就说明中间有一部分开始是乱序的了。

    双指针的做法跟直觉比较冲突的地方是,一开始我们遍历 input 数组的时候,指针是从左往右走的,但是如果 nums[i] < max 我们就移动 right 指针。这里我们把 right 指针理解为需要翻转的部分的右边界,既然当前这个 nums[i] < max 了,那么需要被翻转的右边界应该需要被更新到 i 。而且需要一直扫描直到数组末端,因为有可能后面还有乱序的部分。

    既然数组从左往右是需要递增的,那么从右往左就需要满足递减,所以当我们发现数组从右往左扫描的时候不遵循递减规则的话,就要更新左指针。

    比如第一个例子,数组在遇到4的时候发觉不满足 nums[i] < max了,就更新 right 指针,但是注意他还会接着往下走,所以 right 指针在9的位置上还会再被更新一次。这里是让我觉得这个思路不是非常直观的地方。

    时间O(n)

    空间O(1)

    Java实现

     1 class Solution {
     2     public int findUnsortedSubarray(int[] nums) {
     3         int len = nums.length;
     4         int min = nums[len - 1];
     5         int max = nums[0];
     6         int left = 0;
     7         int right = -1;
     8         for (int i = 0; i < len; i++) {
     9             // 左半边如果递增,则一直更新max
    10             // 否则移动right指针
    11             if (nums[i] < max) {
    12                 right = i;
    13             } else {
    14                 max = nums[i];
    15             }
    16 
    17             // 右半边如果递减,则一直更新min
    18             // 否则移动left指针
    19             if (nums[len - 1 - i] > min) {
    20                 left = len - 1 - i;
    21             } else {
    22                 min = nums[len - 1 - i];
    23             }
    24         }
    25         return right - left + 1;
    26     }
    27 }

    JavaScript实现

     1 /**
     2  * @param {number[]} nums
     3  * @return {number}
     4  */
     5 var findUnsortedSubarray = function (nums) {
     6     let len = nums.length;
     7     let max = nums[0];
     8     let min = nums[len - 1];
     9     let l = 0;
    10     let r = -1;
    11     for (let i = 0; i < nums.length; i++) {
    12         if (nums[i] < max) {
    13             r = i;
    14         } else {
    15             max = nums[i];
    16         }
    17         if (nums[len - 1 - i] > min) {
    18             l = len - 1 - i;
    19         } else {
    20             min = nums[len - 1 - i];
    21         }
    22     }
    23     return r - l + 1;
    24 };

    第二种思路,我们可以扫描两遍,思路跟扫描一遍类似,但是容易理解一些。这里我们定义两个变量 end 和 start,end 代表从左到右扫描的时候最后一个小于 max 的下标。start 表示从右往左扫描的时候最后一个大于 min 的下标。我们分别从左到右,从右到左扫描两次 input 数组,需要重新排序的部分即是 end - start + 1。同时注意 end 变量的初始值是 -2,是为了处理整个 input 数组都是有序的 corner case。

    时间O(n)

    空间O(1)

    Java实现

     1 class Solution {
     2     public int findUnsortedSubarray(int[] nums) {
     3         // corner case
     4         if (nums == null || nums.length == 0) {
     5             return 0;
     6         }
     7         // normal case
     8         int max = Integer.MIN_VALUE;
     9         int end = -2;
    10         for (int i = 0; i < nums.length; i++) {
    11             max = Math.max(max, nums[i]);
    12             if (nums[i] < max) {
    13                 end = i;
    14             }
    15         }
    16 
    17         int min = Integer.MAX_VALUE;
    18         int start = -1;
    19         for (int i = nums.length - 1; i >= 0; i--) {
    20             min = Math.min(min, nums[i]);
    21             if (nums[i] > min) {
    22                 start = i;
    23             }
    24         }
    25         return end - start + 1;
    26     }
    27 }

    第三种思路是跟已经排好序的结果进行对照,找到左右两边第一次开始不一样的地方,中间夹住的部分就是需要翻转的部分。

    时间O(nlogn)

    空间O(n)

    Java实现

     1 class Solution {
     2     public int findUnsortedSubarray(int[] nums) {
     3         int len = nums.length;
     4         int[] temp = nums.clone();
     5         Arrays.sort(temp);
     6 
     7         int start = 0;
     8         while (start < len && nums[start] == temp[start]) {
     9             start++;
    10         }
    11         if (start == len) {
    12             return 0;
    13         }
    14 
    15         int end = len - 1;
    16         while (end > start && nums[end] == temp[end]) {
    17             end--;
    18         }
    19 
    20         return end - start + 1;
    21     }
    22 }

    LeetCode 题目总结

  • 相关阅读:
    Vs code 通用插件
    VS Code 使用小技巧
    vscode: Visual Studio Code 常用快捷键
    AngularJS 和 Electron 构建桌面应用
    设计模式(四)简单工厂模式
    java必备——经典的Hibernate
    操作系统之分页分段介绍
    Js 标签云
    Android多线程分析之中的一个:使用Thread异步下载图像
    033 调整数组顺序使奇数位于偶数前面(keep it up)
  • 原文地址:https://www.cnblogs.com/cnoodle/p/13694562.html
Copyright © 2011-2022 走看看