最短无序连续子数组
1.题目描述
给定一个整数数组,你需要寻找一个连续的子数组,如果对这个子数组进行升序排序,那么整个数组都会变为升序排序。
你找到的子数组应是最短的,请输出它的长度。
示例 1:
输入: [2, 6, 4, 8, 10, 9, 15]
输出: 5
解释: 你只需要对 [6, 4, 8, 10, 9] 进行升序排序,那么整个表都会变为升序排序。
2.题解
2.1 暴力
public int findUnsortedSubarray(int[] nums) {
int res = nums.length;
for (int i = 0; i < nums.length; i++) {
for (int j = i; j <= nums.length; j++) {
int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE, prev = Integer.MIN_VALUE;
for (int k = i; k < j; k++) {
min = Math.min(min, nums[k]);
max = Math.max(max, nums[k]);
}
if ((i > 0 && nums[i - 1] > min) || (j < nums.length && nums[j] < max))
continue;
int k = 0;
while (k < i && prev <= nums[k]) {
prev = nums[k];
k++;
}
if (k != i)
continue;
k = j;
while (k < nums.length && prev <= nums[k]) {
prev = nums[k];
k++;
}
if (k == nums.length) {
res = Math.min(res, j - i);
}
}
}
return res;
}
数组[2, 6, 4, 8, 10, 9, 15]
的最短无序连续子数组为nums[1:6]
,即[6, 4, 8, 10, 9]
。
nums[0]
和nums[6]
是升序的。子数组nums[1:6]
的最小值大于nums[0]
,最大值小于nums[6]
。所以,只要对nums[1:6]
这个子数组进行升序排序,那么整个数组都会变为升序排序。
解释代码:
通过两层循环遍历所有可能的子序列。
遍历到子序列[2, 6, 4]
时,由于4<6
,所以需要对[2, 6, 4]
排序。
同理,遍历到子序列[2, 6, 4, 8, 10, 9]
时,由于9<10
,也需要对[2, 6, 4, 8, 10, 9]
排序。
if ((i > 0 && nums[i - 1] > min) || (j < nums.length && nums[j] < max))
continue;
下面这部分代码用于检查nums[j:n−1]
是否是升序的,如果nums[j:n−1]
是升序的,那么k == nums.length
,就可以求子数组nums[i:j]
的长度。
int k = 0;
// ...
k = j;
while (k < nums.length && prev <= nums[k]) {
prev = nums[k];
k++;
}
if (k == nums.length) {
res = Math.min(res, j - i);
}
下面这部分代码用于检查nums[0:i−1]
和nums[j:n−1]
是否是升序的。
int k = 0;
while (k < i && prev <= nums[k]) {
prev = nums[k];
k++;
}
// ...
k = j;
while (k < nums.length && prev <= nums[k]) {
prev = nums[k];
k++;
}
if (k == nums.length) {
res = Math.min(res, j - i);
}
最后可以求得全局最短无序连续子数组。
2.2 更好的暴力
// [2, 6, 4, 8, 10, 9, 15]
public int findUnsortedSubarray(int[] nums) {
int l = nums.length, r = 0;
for (int i = 0; i < nums.length - 1; i++) {
for (int j = i + 1; j < nums.length; j++) {
if (nums[j] < nums[i]) {
r = Math.max(r, j);
l = Math.min(l, i);
}
}
}
return r - l < 0 ? 0 : r - l + 1;
}
显然nums[0]
在数组中的正确位置上。
当i = 1, j = 2
时,由于nums[1]<nums[2](即6<4)
,所以nums[1]
不在数组中的正确位置上,更新无序子数组的左边界为1
,右边界为2
。
当i = 4, j = 5
时,同理,由于nums[4]<nums[5]
,更新无序子数组的左边界为1
,右边界为5
。
实际上,对于数组[2, 6, 4, 8, 10]
,对其子数组[6, 4]
进行升序排序即可。而对于[2, 6, 4, 8, 10, 9]
,需要对其子数组[6, 4, 8, 10, 9]
进行升序排序。
注意到对于数组[15, 4, 6, 8, 9, 10, 2]
,即使其子数组[4, 6, 8, 9, 10]
是升序排序的,也需要对整个数组进行升序排序。
2.3 排序
// [2, 6, 4, 8, 10, 9, 15]
public int findUnsortedSubarray(int[] nums) {
int[] snums = nums.clone();
Arrays.sort(snums);
int start = snums.length, end = 0;
for (int i = 0; i < snums.length; i++) {
if (snums[i] != nums[i]) {
start = Math.min(start, i);
end = Math.max(end, i);
}
}
return (end - start >= 0 ? end - start + 1 : 0);
}
对原数组进行升序排序后,对比原数组和排序后的数组,如果某个位置上的元素不相等,说明原数组中该位置的元素不在正确的位置上。
2.4 使用栈
// [2, 6, 4, 8, 10, 9, 15]
public int findUnsortedSubarray(int[] nums) {
Stack < Integer > stack = new Stack < Integer > ();
int l = nums.length, r = 0;
for (int i = 0; i < nums.length; i++) {
while (!stack.isEmpty() && nums[stack.peek()] > nums[i])
l = Math.min(l, stack.pop());
stack.push(i);
}
stack.clear();
for (int i = nums.length - 1; i >= 0; i--) {
while (!stack.isEmpty() && nums[stack.peek()] < nums[i])
r = Math.max(r, stack.pop());
stack.push(i);
}
return r - l > 0 ? r - l + 1 : 0;
}
注意到对于数组[6, 2, 4, 8, 10, 15, 9]
,需要对整个数组进行升序排序,因为nums[0]
和nums[6]
都不在正确的位置上。
2.5 不使用额外空间
// [2, 6, 4, 8, 10, 9, 15]
public int findUnsortedSubarray(int[] nums) {
int min = Integer.MAX_VALUE, max = Integer.MIN_VALUE;
boolean flag = false;
for (int i = 1; i < nums.length; i++) {
if (nums[i] < nums[i - 1])
flag = true;
if (flag)
min = Math.min(min, nums[i]);
}
flag = false;
for (int i = nums.length - 2; i >= 0; i--) {
if (nums[i] > nums[i + 1])
flag = true;
if (flag)
max = Math.max(max, nums[i]);
}
int l, r;
for (l = 0; l < nums.length; l++) {
if (min < nums[l])
break;
}
for (r = nums.length - 1; r >= 0; r--) {
if (max > nums[r])
break;
}
return r - l < 0 ? 0 : r - l + 1;
}
注意到nums[0]
和nums[6]
都在正确的位置上了,于是考虑子数组[6, 4, 8, 10, 9]
。
由于6<4
和10<9
,所以min
为4
,max
为10
。
在无序子数组中,其最小元素应该放到该数组的第一个位置,其最大元素应该放到该数组的最后一个位置。
因此,第一个大于4
的元素的位置是无序子数组的第一个位置,逆序遍历第一个小于10
的元素的位置是无序子数组的最后一个位置。
以[2, 4, 9, 10, 8, 15]
为例,由于10>8
,min
为8
,max
为10
,第一个大于8
的为9
,逆序遍历第一个小于10
的为8
,所以[9, 10, 8]
为最短无序连续子数组。
参考: