最短子数组
问题描述:
给定一个数组a和数组长度n,求出需要排序的最短子数组长度,使得该子数组排好序时整个数组有序。
测试样例:
- 输入:a={1,4,6,5,9,10}, n=6
- 输出:2
问题分析:
由题意知,我们需要找到一个子数组使得当该子数组排好序时整个数组有序。这就是说原数组可以分为三个部分,即a=pqk,此时我们找到的子数组为q。p和r已经分别有序且p中所有元素小于q中任意一个元素,k中任意一个元素大于q中所有元素。
为了做到这点,我们可以简单的枚举出所有的子数组q(长度从小到大),然后按照上面的思路进行验证。当枚举到某个子数组q时,该子数组前面的元素有序且小于q中任意一个元素,需要时间开销:
|p|+|p|×|q|
若对其优化,只用|p|中最大值验证是否小于|q|中元素,需要时间开销为:
|p|+|q|
该子数组后面的元素有序且这些元素中任意一个元素都大于q中所有元素,需要时间开销:
|k|+|q|×|k|
若对其优化,只用|k|中最小值验证是否大于|q|中元素,需要时间开销:
|k|+|q|
则子数组q的长度即为所求,此时每一次处理需要的时间开销大致为:
|q|×(|p|+|k|)=|q|×(n−|q|)
优化后大致为:
|p|+|q|+|k|=n
所以这种方法的时间复杂度是:
∑|q|=1n|q|(n−|q|)2
优化后为:
∑|q|=1nn(n−|q|)=n3−n2(n+1)2
即O(n^3)级别。
为了能获得更好的时间开销算法,我们可以对数组进行两次扫描。第一次从左往右扫描找到最大值不变的最后一个位置r(注意扫描时最大值在不断被更正),对于上述测试样例,该策略的处理步骤是:
首先最大值是1,r为0;接下来最大值是4,此时最大值改变了,也就是说r不用变;同理最大值是6时r也不变;当扫描到5时由于最大值没有改变,故r更新为3;之后最大值一直在改变,所以r最终等于3。
第二次从右往左扫描找到最小值不变的最后一个位置l,对于上述测试样例,l的值为2。所以p={1,4},q={6,5},k={9,10},此时q的长度为2,故返回2。显然这种算法的时间复杂度为O(n),比第一种枚举策略更高效。
下面给出在java代码下的实现:
int shortestSubsequence(int[] A, int n) {
if(A==null || n<=0)
return 0;
int i, j, l, r;
j = A[0];
r = 0;
for (i = 1; i < n; i++) {
if (A[i] < j)
r = i;
else
j = A[i];
}
if (r == 0)
return 0;
j = A[n - 1];
l = n - 1;
for (i = n - 2; i >= 0; i--) {
if (A[i] > j)
l = i;
else
j = A[i];
}
return r - l + 1;
}