原创🌶🐤解法
对数组排序,记录每个值【应该在】的位置;维护一个左右边界,遍历原数组,遇到逆序的元素,就用 [有序数组中的位置, 原数组中的位置] 更新边界值。
var findUnsortedSubarray = function (nums) {
if (nums.length === 1) {
return 0;
}
// 这里注意 sort 会更改原数组,所以复制一个出来
let arr = [...nums];
let sortedNums = arr.sort((a, b) => a - b);
let posMap = new Map();
// 用来保存每个值的位置,因为有重复元素,value是一个数组
for (let i in sortedNums) {
if (!posMap.get(sortedNums[i])) {
posMap.set(sortedNums[i], [i]);
}else{
posMap.get(sortedNums[i]).push(i)
}
}
let left = nums.length - 1;
let right = 0;
let max = Number.MIN_SAFE_INTEGER;
// 更新左右边界
for (let i in nums) {
if (nums[i] < max) {
// 左边取最小值
// 对于重复元素,我们优先考虑大的下标作为无序区间的start
// 比如 [2, 6, 8, 2] ,实际上是 1 的位置,而不是 0
// [6, 8, 2, 2],用了 1 之后,再用 0
left = Math.min(left, posMap.get(nums[i]).shift());
right = i;
}
max = Math.max(max, nums[i]);
}
return right + left + 1 === nums.length ? 0 : right - left + 1;
};
大佬的O(n)
从左开始找升序列,从右找降序列;获得无序子序列的边界;找这个无序序列的 min 和 max,将 min 和 max 归位,归位的位置构成的区间就是结果。
var findUnsortedSubarray = function (nums) {
let len = nums.length;
if (len === 1) {
return 0;
}
let left = 0;
let right = nums.length - 1;
// 寻找有序区间的边界
for (let i = 0; i < len; i++) {
if (nums[i] >= nums[left]) {
left = i;
} else {
break;
}
}
for (let j = right; j >= left; j--) {
if (nums[j] <= nums[right]) {
right = j;
} else {
break;
}
}
// 数组本身有序
if (left === right) {
return 0;
}
// 找无序区间的 min 和 max
let min = nums[left];
let max = nums[right];
for (let m = left; m <= right; m++) {
min = Math.min(min, nums[m]);
max = Math.max(max, nums[m]);
}
// 归位
let leftFlag = 0;
let rightFlag = 0;
// 移动指针,直到最小值 min 大于等于数组中的某个值
// 边界为 -1 是考虑到 left = 0 的情况
for (let i = left; i >= -1; i--) {
if (min < nums[i]) {
leftFlag = i;
}
}
// 移动指针,直到最大值 max 小于等于数组中的某个值
for (let j = right; j <= nums.length; j++) {
if (max > nums[j]) {
rightFlag = j;
}
}
return rightFlag - leftFlag + 1;
};
大佬最终版
再来思考一下【逆序】为何物:
逆序,说明下标为 n 的当前值需要插入到前面的序列[0, ..., n - 1]中才能让整个序列有序。那么,如果小于前面序列任意一值,就称之为【逆序】。由于阈值越大,当前值越容易小于它,所以我们应该用最大值 max 作为判断是否逆序的条件。
这样就能找到最右边的【逆序数】,再往右,数组就有序了。那么该如何获得区间长度呢?
直接算这个数到归位之间的距离?不行,因为它之前还可能有其它需要归位的数。
这个时候其实我们需要的是一个最左边的【逆序数】,再往左,就没有逆序数了。
同理,我们从右边出发,用最小值 min 来判断是否逆序。
var findUnsortedSubarray = function (nums) {
let p2 = 0;
let p1 = 0;
let max = nums[0];
let min = nums[nums.length - 1];
for (let i = 0; i < nums.length; i++) {
max = Math.max(max, nums[i]);
// 发现小于 max 的时候就会赋值给 p1
if (nums[i] < max) {
p1 = i;
}
}
for (let i = nums.length - 1; i >= 0; i--) {
min = Math.min(min, nums[i]);
// 发现大于 min 的时候就会赋值给 p2
if (nums[i] > min) {
p2 = i;
}
}
const diff = p1 - p2;
return diff > 0 ? diff + 1 : diff;
};
咦?这怎么和【解法2】中从前后找有序序列的操作这么相似??在这个用例里面直接right - left + 1就可以啊。
但如果是 [2, 3, 4, 2, 5],这两种边界就不会重合。一个是[3, 3],一个是[1, 3]。
本质还是一个问题:【解法2】中,无法知道中间的乱序序列会如何影响到两边的序列。而【解法3】中,p1 和 p2 边界是和两边序列的 max 、 min 比较过的,所以能决定边界。