1.题目
2.思路
方法一1ms
看见这个数据范围,最优解通常就是O(n),有时候还会是O(nlogn)。所以我们想着往同一个O(n)的思路上靠,也就是只有一层遍历。
这里的题目感觉少了一个条件。题目的意思应该是让数组变成升序的最短序列。
我们来思考一个问题,如何确定左边界的呢?也就是第一个例子中如何确定是从左边位置3开始的呢?
第一次遍历:
我们按照从左到右进行遍历,如果发现了nums[i]不再大于nums[i - 1],说明数组就不是完全升序的。此时我们需要记录从nums[i]到nums[n - 1]中间的所有数字的最小值min.(题目中为6)
第二次遍历:
从左到右,我们找到刚好大于这个min的数字,记录这个位置,就是左边界3
同样的,我们采用类似的方法确定好有边界的位置。
第三次遍历:
从右边往左边遍历,找到nums[i] 不再小于nums[i + 1],说明此时数组不是完全升序的。我们此时记录下从nums[i]到nums[0]的中所有数字的最大值max.(题目中为12)
第四次遍历:
从右往左遍历,我们找到刚好小于这个max的数字,记录这个位置,就是右边界9.
最终返回【3,9】。
- 时间复杂度:O(n)
- 空间复杂度:O(1)
class Solution {
public int[] subSort(int[] array) {
int n = array.length;
if(n <= 1) return new int[]{-1, -1};
int leftValue = Integer.MAX_VALUE, rightValue = Integer.MIN_VALUE;
int leftIndex = -1, rightIndex = -1;
int flag = 0;
for(int i = 1 ; i < n ; i++){
if(flag == 0 && array[i] >= array[i - 1])
continue;
else{
flag = 1;
if(leftValue > array[i]){
leftValue = array[i];
}
}
}
// 代表数组有序
if(flag == 0) return new int[]{-1, -1};
// 找到最小的leftIndex
for(int i = 0 ; i < n ; i++){
if(array[i] <= leftValue)
continue;
else{
leftIndex = i;
break;
}
}
flag = 0;
for(int i = n - 2 ; i >= 0 ; i--){
if(flag == 0 && array[i + 1] >= array[i])
continue;
else{
flag = 1;
if(rightValue < array[i]){
rightValue = array[i];
}
}
}
// System.out.println(leftValue + ">>>" + rightValue);
// 找到最小的rightIndex
for(int i = n - 1 ; i >= 0 ; i--){
if(array[i] >= rightValue)
continue;
else{
rightIndex = i;
break;
}
}
return new int[]{leftIndex, rightIndex};
}
}
- 注意特殊情况,提早结束。比如n <= 1.,以及都是升序的时候。
- 注意多测试一些数据,比如相等的时候,以及全部都是升序的时候,全部都是降序的时候,以及随机数字。
方法二2ms
其实方法一的四次遍历,是可以减少到两次的。只不过思路稍微不太一样。参考了题解的方法,相当于是头尾遍历。
第一次遍历:从左往右遍历,目标是为了确定右边界。(而方法一是为了确定左边界。)
第二次遍历:从右往左遍历,目标是为了确定左边界。
那么是怎么确定的呢?以右边界为例子。
我们维护一个从左到右的最大值leftValueMax,如果当前的值nums[i]比这个最大值小,说明不是完全有序的,那么更新右边界rightIndex.否则,就更新最大值leftValueMax.
当从左往右遍历完毕时,也就找到了最右边的边界rightIndex.
同理,我们可以从右往左遍历,然后维护一个最小值rightValueMin.
最后可以把这两次遍历的代码写在一起,同时头尾遍历和处理,不会影响。本质上仍然是两次遍历。
- 时间复杂度:O(n)
- 空间复杂度:O(1)
class Solution {
public int[] subSort(int[] nums) {
// 头尾遍历
int n = nums.length;
if(n <= 1) return new int[]{-1 , -1};
int leftValueMax = nums[0], rightValueMin = nums[n - 1];
int leftIndex = -1, rightIndex = -1;
for(int i = 0 ; i < n ; i++){
// 从左往右:如果当前元素小于 0 -- i 之间的最大值,说明不是完全升序,更新rightIndex
// 从右往左:如果当前元素大于 n-1-i -- n-1之间的最小值,说明不是完全降序,更新leftIndex
if(nums[i] < leftValueMax)
rightIndex = i;
else
leftValueMax = nums[i];
if(nums[n - i - 1] > rightValueMin)
leftIndex = n - i - 1;
else
rightValueMin = nums[n - i - 1];
// System.out.println(leftIndex + ">>" + rightIndex);
}
return new int[]{leftIndex, rightIndex};
}
}
3.思考
其实不管是从左到右,还是从右到左遍历,每次的选择都能保证是当前的最佳选择,所以遍历完后,就是全局最优,所以就是贪心的思想。