leetcode:581. 最短无序连续子数组
题目
给你一个整数数组 nums ,你需要找出一个 连续子数组 ,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
请你找出符合题意的 最短 子数组,并输出它的长度。
示例:
输入:nums = [2,6,4,8,10,9,15]输出:5解释:你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。
输入:nums = [1,2,3,4]输出:0
输入:nums = [1]输出:0
解答 & 代码
解法一:排序
class Solution {public:int findUnsortedSubarray(vector<int>& nums) {// 0. 拷贝创建一个数组,并排序vector<int> sortedNums = nums;sort(sortedNums.begin(), sortedNums.end());// 1. 找到最左边的无序位置 left,// 即 [0, left) 的元素都在其正确位置,排序后位置不变int left = 0;while(left < nums.size() && nums[left] == sortedNums[left])++left;// 如果 left 越界,说明数组已经是有序的,最短无序连续子数组长度为 0,直接返回if(left == nums.size())return 0;// 2. 找到最右边的无序位置 right,// 即 (right, len - 1] 的元素都在其正确位置,排序后位置不变int right = nums.size() - 1;while(right >= 0 && nums[right] == sortedNums[right])--right;// 返回 最短无序连续子数组 长度return right - left + 1;}};
复杂度分析:设数组长为 N
- 时间复杂度 O(NlogN):排序的时间复杂度 O(NlogN),后序两次遍历寻找
left、right位置的时间复杂度 O(N) - 空间复杂度 O(N):额外设置了一个等长的数组来存储排序后的数组元素
执行结果:
执行结果:通过执行用时:36 ms, 在所有 C++ 提交中击败了 19.09% 的用户内存消耗:26.7 MB, 在所有 C++ 提交中击败了 24.69% 的用户
解法二:两端往里遍历

如上图所示,可以将数组分为左、中、右三段,左段和有段都是有序的(单调递增),只有中段是无序的,中段的长度 right - left + 1 就是最短无序连续子数组的长度。因此,我们要找到无序子数组的左边界 left 和右边界 right。
- 逆序遍历寻找中段的左边界
**left**:可以发现,左段是有序的,所以左段每个元素都小于其任意一个右侧元素,也就是说左段的每个元素都比其右侧的最小元素小。那么就可以从右往左遍历,实时得到当前位置i右侧的最小元素值rightMin,如果nums[i] > rightMin,那么i肯定在中段,因此更新中段的左边界left = i。逆序遍历完数组后,最终left指向的元素就是最左边一个不满足小于其右侧最小元素的,就是中段的左边界。 正序遍历寻找中段的右边界
**right**:同理,右段是有序的,所以右段的每个元素都大于其任意一个左侧元素,也就是说右段的每个元素都比其左侧的最大元素大。那么就可以从左往右便利,实时得到当前位置i左侧的最大元素值leftMax,如果nums[i] < rightMax,那么i肯定在中段,因此更新中段的右边界right = i。 正序遍历完数组后,最终 right 指向的元素就是最右边一个不满足大于其左侧最大元素的,就是中段的右边界。class Solution {public:int findUnsortedSubarray(vector<int>& nums) {int len = nums.size();int rightMin = INT_MAX; // 右侧的最小值int left = -1; // 中段(无序子数组)的左边界// 1. 逆序遍历寻找中段的左边界(最左边不满足 小于其右侧最小值 的元素所在的位置)for(int i = len - 1; i >= 0; --i){// 如果当前元素 nums[i] 不满足小于右侧最小值 rightMin,// 说明当前元素位于中段,则更新左边界 leftif(nums[i] > rightMin)left = i;// 否则,更新右侧最小值 rightMinelserightMin = nums[i];}// 注意:特判:如果数组本身是有序的,遍历完后中段左边界仍为 -1,直接返回 0if(left == -1)return 0;int leftMax = INT_MIN; // 左侧的最大值int right = -1; // 中段(无序子数组)的右边界// 2. 正序遍历寻找中段的右边界(最右边不满足 大于其左侧最大值 的元素所在的位置)for(int i = 0; i < len; ++i){// 如果当前元素 nums[i] 不满足大于其左侧最大值 leftMax,// 说明当前元素位于中段,则更新右边界 rightif(nums[i] < leftMax)right = i;// 否则,更新左侧最大值 leftMaxelseleftMax = nums[i];}// 返回中段(最短无序连续子数组)的长度return right - left + 1;}};
复杂度分析:
- 时间复杂度 O(N)
- 空间复杂度 O(1)
执行结果:
执行结果:通过执行用时:16 ms, 在所有 C++ 提交中击败了 97.97% 的用户内存消耗:25.9 MB, 在所有 C++ 提交中击败了 59.11% 的用户
