Problem
Solution
题意为:给定一个数组,找出其中的最短未排序连续子串。
何为最短未排序连续子串?就是只要把这个子串排完序,整个原数组就可以达到有序的状态。
e.g [2,6,4,8,10,9,15]这个序列,我们只要把中间的子串:[6, 4, 8, 10, 9]排好序,整个原数组就有序了。
我们不需要排序的元素就是排序前和排序后在同一位置上的元素,并且因为要求找出的是一个连续子串,那么我们只需要找出最左边和最右边的排序前和排序后下标改变的元素就可以了。
一、sort方法
直接调用sort对原数组排序,在线性扫描对比,找出第一个不同和最后一个不同,就找到了要求的子串。
排序复杂度o(nlogn)
class Solution {
public:
int findUnsortedSubarray(vector<int>& nums) {
vector<int> sorted(nums.begin(),nums.end());
sort(sorted.begin(),sorted.end());
int start=0,end=0;
for(int i=0;i<nums.size();++i)
if(nums[i]!=sorted[i]){
start=i;break;
}
for(int i=nums.size()-1;i>=0;--i)
if(nums[i]!=sorted[i]){
end=i;break;
}
return end-start==0?0:end-start+1;
}
};
二、模拟选择排序的过程,找出最左和最右边界
该题的整个过程就像选择排序一样,每次都在剩余元素中选出最小值,放在已经有序的子串后面,重复这个过程,就可以达到整体有序。
这种思路我一开始也想到了,但实现的时候没想清楚,导致效率很低。
我的具体实现是如果当前元素是逆序的,那么我就从头开始查找他应该插入的位置。时间o(n^2)。
而在本文后面我们可以看到,其实并不用对每个逆序元素都找出该插入的位置,而只需要找出最小的元素的插入位置就可以确定左边界,找出最大的元素的插入位置就可以确定右边界。
class Solution {
public:
int findUnsortedSubarray(vector<int>& nums) {
int n=nums.size();
if(!n) return 0;
int start=n,end=0;
for(int i=1;i<n;++i){
if(nums[i]<nums[i-1]){
for(int j=0;j<i;++j){
if(nums[j]>nums[i]){
start=start<j?start:j;
break;
}
}
}
}
for(int i=n-2;i>=0;--i){
if(nums[i]>nums[i+1]){
for(int j=n-1;j>i;--j){
if(nums[j]<nums[i]){
end=end>j?end:j;
break;
}
}
}
}
return start==n?0:end-start+1;
}
};
更高效的实现方法:
利用栈,可以把时间复杂度降到o(n)
从0~n循环,把下标压栈,如果出现了逆序,就出栈到栈顶元素小于当前元素为止,这样就找到了左边界
右边界就是循环顺序反一下,判断条件反一下,就可以找到右边界
class Solution {
public:
int findUnsortedSubarray(vector<int>& nums) {
int n=nums.size();
int l=n,r=0;
stack<int> st;
for(int i=0;i<n;++i){
while(!st.empty()&&nums[st.top()]>nums[i]){
l=l<st.top()?l:st.top();
st.pop();
}
st.push(i);
}
while(!st.empty()) st.pop();
for(int i=n-1;i>=0;--i){
while(!st.empty()&&nums[st.top()]<nums[i]){
r=r>st.top()?r:st.top();
st.pop();
}
st.push(i);
}
return l==n?0:r-l+1;
}
};
还可以进一步降低空间复杂度
其实想明白了就知道,我们只需要找到这个需排序子串里的最小值和最大值,再去找他们在原串里的位置,就是左边界和右边界了。并不需要记录其他的情况。这样我们就可以把空间降到o(1)。
class Solution {
public:
int findUnsortedSubarray(vector<int>& nums) {
int minnum=INT_MAX,maxnum=INT_MIN;
int n=nums.size();
bool flag=false;
for(int i=1;i<n;++i){
if(nums[i]<nums[i-1]) flag=true;
if(flag) minnum=minnum<nums[i]?minnum:nums[i];
}
flag=false;
for(int i=n-2;i>=0;--i){
if(nums[i]>nums[i+1]) flag=true;
if(flag) maxnum=maxnum>nums[i]?maxnum:nums[i];
}
int l,r;
for(l=0;l<n;++l){
if(minnum<nums[l]) break;
}
for(r=n-1;r>=0;--r){
if(maxnum>nums[r]) break;
}
return l>=r?0:r-l+1;
}
};