最短无序连续子数组

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<410<9,所以min4max10
在无序子数组中,其最小元素应该放到该数组的第一个位置,其最大元素应该放到该数组的最后一个位置。
因此,第一个大于4的元素的位置是无序子数组的第一个位置,逆序遍历第一个小于10的元素的位置是无序子数组的最后一个位置。
[2, 4, 9, 10, 8, 15]为例,由于10>8min8max10,第一个大于8的为9,逆序遍历第一个小于10的为8,所以[9, 10, 8]为最短无序连续子数组。

参考:

posted @ 2020-12-28 14:45  gzhjj  阅读(205)  评论(0编辑  收藏  举报