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,
// 说明当前元素位于中段,则更新左边界 left
if(nums[i] > rightMin)
left = i;
// 否则,更新右侧最小值 rightMin
else
rightMin = nums[i];
}
// 注意:特判:如果数组本身是有序的,遍历完后中段左边界仍为 -1,直接返回 0
if(left == -1)
return 0;
int leftMax = INT_MIN; // 左侧的最大值
int right = -1; // 中段(无序子数组)的右边界
// 2. 正序遍历寻找中段的右边界(最右边不满足 大于其左侧最大值 的元素所在的位置)
for(int i = 0; i < len; ++i)
{
// 如果当前元素 nums[i] 不满足大于其左侧最大值 leftMax,
// 说明当前元素位于中段,则更新右边界 right
if(nums[i] < leftMax)
right = i;
// 否则,更新左侧最大值 leftMax
else
leftMax = nums[i];
}
// 返回中段(最短无序连续子数组)的长度
return right - left + 1;
}
};
复杂度分析:
- 时间复杂度 O(N)
- 空间复杂度 O(1)
执行结果:
执行结果:通过
执行用时:16 ms, 在所有 C++ 提交中击败了 97.97% 的用户
内存消耗:25.9 MB, 在所有 C++ 提交中击败了 59.11% 的用户