Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。
题目:给定一个整数数组nums,要求找到一个连续子数组,要求对这个连续子数组进行排序,则整个序列变成有序,输出最短子数组的长度。
解题思路
最简单的思路,调用系统函数排序,我们可以先将数组克隆,将克隆后的数组进行排序,比较两个数组中元素是否相等即可,记录不相等的元素索引,判断索引即可得到最终结果。
寻找索引可以采用两次循环,第一个循环从头往后寻找,只要找到了就赋值break。另一个循环从后往前,找到了也直接break。
实际上,寻找两个索引只需要进行一次for循环,通过一轮轮的替换,必然不相等的元素索引会被记录,代码如下:
public int findUnsortedSubarray(int[] nums) {
int left = -1, right = -1;
int[] clone = nums.clone();
Arrays.sort(clone);
for(int i=0;i<nums.length;i++){
if(nums[i]!=clone[i]){
left = i;
}
if(nums[nums.length-i-1]!=clone[nums.length-i-1]){
right = nums.length-i-1;
}
}
if(left==-1) return 0;
return left - right + 1;
}
时间复杂度为, 空间复杂度, 最终耗时7ms。
思路进阶
我们看一个有序数组:[1, 2, 3, 4, 5],在数组中的每个元素存在这样一个特点:
- 每个元素都大于前面所有元素,并且小于后面所有元素。
根据此特征,我们是否可以用两个数组分别记录当前元素前面的最大值和后面的最小值,因为未排序的数存在的特征就是此数后面存在比此数小的,而前面存在比此数大的。之后遍历数组将元素分别和这两个数组元素进行比对即可。可得代码如下:
public int findUnsortedSubarray2(int[] nums) {
int len = nums.length;
boolean[] sort = new boolean[len];
int[] left = new int[len]; // 截至当前元素左边最大的
left[0] = nums[0];
int[] right = new int[len]; // 截至当前元素右边最小的
right[len - 1] = nums[len - 1];
for (int i = 1; i < len; i++) {
left[i] = Math.max(nums[i], left[i - 1]);
}
for (int i = len - 2; i >= 0; i--) {
right[i] = Math.min(nums[i], right[i + 1]);
}
for (int i = 0; i < len; i++) {
if (nums[i] >= left[i] && nums[i] <= right[i]) {
sort[i] = true;
} else {
sort[i] = false;
}
}
int l = -1, r = -1;
for (int i = 0; i < len; i++) {
if (!sort[i]) {
l = i;
break;
}
}
for (int i = len - 1; i >= 0; i--) {
if (!sort[i]) {
r = i;
break;
}
}
if (r == -1 && l == -1) return 0;
return r - l + 1;
}
时间复杂度为,空间复杂度也是,最终耗时2ms。代码较长,但思路尚且清晰。实际上上述代码还可以优化,但最终结果不会差多少。
最佳答案
根据有序数组的特征:每个元素都大于前面所有元素,并且小于后面所有元素。我们可以得到最终的答案必然是划分成了三个区域:
| 有序 | 无序 | 有序 |
因此我们只需找到前面序列的有序序列和后面有序序列即可判定最终的无序序列。寻找有序序列很简单,一个个判定即可,代码如下:
public int findUnsortedSubarray(int[] nums) {
int max = Integer.MIN_VALUE, right = -1;
int min = Integer.MAX_VALUE, left = -1;
for(int i=0;i<nums.length;i++){
if(max>nums[i]){
right=i;
}else {
max = nums[i];
}
}
for(int i=nums.length-1;i>=0;i--){
if(min<nums[i]){
left=i;
}else {
min = nums[i];
}
}
if(left==-1) return 0;
return right-left+1;
}
最终耗时0ms,上面两个for循环也可进行合并,如下:
public int findUnsortedSubarray3(int[] nums) {
int max = Integer.MIN_VALUE, right = -1;
int min = Integer.MAX_VALUE, left = -1;
for(int i=0;i<nums.length;i++){
if(max>nums[i]){
right=i;
}else {
max = nums[i];
}
if(min<nums[nums.length-i-1]){
left=nums.length-i-1;
}else {
min = nums[nums.length-i-1];
}
}
if(left==-1) return 0;
return right-left+1;
}