[每日一题] 111. 最短无序连续子数组(数组、遍历、多方法)

1. 题目来源

链接:最短无序连续子数组
来源:LeetCode

2. 题目说明

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

你找到的子数组应是最短的,请输出它的长度。

进阶:
你能否仅使用O(1) 空间解决问题?

示例1:

输入: [2, 6, 4, 8, 10, 9, 15]
输出: 5
解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。

说明 :

  1. 输入的数组长度范围在 [1, 10,000]。
  2. 输入的数组可能包含重复元素 ,所以升序的意思是<=。

3. 题目解析

方法一:朴素解法,思路简单但有点绕

这道题给了一个数组,让求最短的无序连续子数组,根据题目中的例子也不难分析出来是让找出数组中的无序的部分。

那么最开始的想法就是要确定无序子数组的起始和结束位置,这样就能知道子数组的长度了。所以用一个变量start来记录起始位置,然后开始遍历数组,当发现某个数字比其前面的数字要小的时候,说明此时数组不再有序,所以要将此数字向前移动,移到其应该在的地方,并用另一个变量j来记录移动到的位置,然后考虑要不要用这个位置来更新start的值:

  • 当start还是初始值-1时,肯定要更新,因为这是出现的第一个无序的地方,
  • 如果当前位置小于start也要更新,这说明此时的无序数组比之前的更长了。

我们举个例子来说明,比如数组[1,3,5,4,2],第一个无序的地方是数字4,它移动到的正确位置是坐标2,此时start更新为2,然后下一个无序的地方是数字2,它的正确位置是坐标1,所以此时start应更新为1,这样每次用i - start + 1来更新结果res时才能得到正确的结果,参见代码如下:

class Solution {
public:
    int findUnsortedSubarray(vector<int>& nums) {
        int res = 0, start = -1, n = nums.size();
        for (int i = 1; i < n; ++i) {
            if (nums[i] < nums[i - 1]) {
                int j = i;
                while (j > 0 && nums[j] < nums[j - 1]) {
                    swap(nums[j], nums[j - 1]);
                    --j;
                }
                if (start == -1 || start > j) start = j;
                res = i - start + 1;
            }
        }
        return res;
    }
};
方法二:辅助数组解法

这种方法是用了一个辅助数组,我们新建一个跟原数组一模一样的数组,然后排序。从数组起始位置开始,两个数组相互比较,当对应位置数字不同的时候停止,同理再从末尾开始,对应位置上比较,也是遇到不同的数字时停止,这样中间一段就是最短无序连续子数组了,参见代码如下:

class Solution {
public:
    int findUnsortedSubarray(vector<int>& nums) {
        int n = nums.size(), i = 0, j = n - 1;
        vector<int> t = nums;
        sort(t.begin(), t.end());
        while (i < n && nums[i] == t[i]) ++i;
        while (j > i && nums[j] == t[j]) --j;
        return j - i + 1;
    }
};
方法三:辅助数组进阶版解法

下面这种方法很叼啊,是O(n)的时间复杂度加上O(1)的空间复杂度,觉得这实际上是对上面的那种方法进行空间上的优化的结果。

用两个变量mx和mn来代替上面的有序数组,仔细来分析发现,

  • 最小值mn初始化为数组的最后一个数字,最大值mx初始化为了第一个数字
  • 然后我们从第二个数字开始遍历,mx和nums[i]之间取较大值赋值给mx,然后比较此时mx和nums[i]之间的大小关系,如果mx大于nums[i],就把i赋值给end,那么我们想如果第一个数字小于第二个,mx就会赋值为第二个数字,这时候mx和nums[i]就相等了,不进行任何操作,因为说明此时是有序的。
  • mn和nums[n-1-i]之间取较小值赋给mn,然后比较此时mn和nums[n-1-i]之间的大小关系,如果mn小于nums[n-1-i],就把n-1-i赋值给start,那么什么时候会进行赋值呢,是当倒数第二个数字大于最后一个数字,这样mn还是最后一个数字,而nums[n-1-i]就会大于mn,这样更新start。

可以看出start是不断往前走的,end是不断往后走的,整个遍历完成后,start和end就分别指向了最短无序连续子数组的起始和结束位置,参见代码如下:

class Solution {
public:
    int findUnsortedSubarray(vector<int>& nums) {
        int n = nums.size(), start = -1, end = -2;
        int mn = nums[n - 1], mx = nums[0];
        for (int i = 1; i < n; ++i) {
            mx = max(mx, nums[i]);
            mn = min(mn, nums[n - 1 - i]);
            if (mx > nums[i]) end = i;
            if (mn < nums[n - 1 - i]) start = n - 1 - i;
        }
        return end - start + 1;
    }
};
发布了209 篇原创文章 · 获赞 42 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/yl_puyu/article/details/104090130
今日推荐