[前端]_一起刷leetcode 面试题 16.16. 部分排序

228 阅读3分钟

这是我参与2022首次更文挑战的第8天,活动详情查看:2022首次更文挑战

题目

面试题 16.16. 部分排序

给定一个整数数组,编写一个函数,找出索引mn,只要将索引区间[m,n]的元素排好序,整个数组就是有序的。注意:n-m尽量最小,也就是说,找出符合条件的最短序列。函数返回值为[m,n],若不存在这样的mn(例如整个数组是有序的),请返回[-1,-1]

示例:

输入: [1,2,4,7,10,11,7,12,6,7,16,18,19]
输出: [3,9]

提示:

  • 0 <= len(array) <= 1000000

思路

  1. 这道题目乍一看挺简单的,无非是数组里面由多个排序好的片段组成,那么我们拆分开后做个归并排序就可以实现统一排序了;
  2. 但是这道题目不是要我们排序,而是要我们求出最少的片段排完后整个有序,看到最少我第一个反应是贪心,左边尽可能多留几个,右边尽可能多留几个,但是我们不确定最多能够留几个,所以这道题目我采用了暴力解法,思路先行;
  3. 我们先把数组切成一个个升序的子数组,如何切割呢,就是当我们的数组当前值小于上一个值的时候,就进行一次切割,这时候我们只需要记录当前的值和上一次的起点所在位置即可,由于题目中没有明确说明是否能够修改原数组,所以我们暂时不做更改,使用复制数组的方式去复制一个个子数组;
  4. 复制完了之后,对边界情况做一个判断,如果只有一个升序数组,说明当前数组不需要再进行排序了,直接返回[-1, -1]即可;
  5. 然后我们要求的是排序最少的中间区间,那么我们可以把问题转换一下。求出后面所有数组的最小值,插入到最前面的指定位置,和求出前面数组的最大值,插入后面数组的指定位置,把区间转换为转换的这两个位置;
  6. 由于我们每个片段都是按照升序排序好的子数组,所以我们只需要拿每个片段的第一个元素就是最小值,最后一个元素就是最大值了,然后求出整个区间内我们所要的最小值和最大值;
  7. 把后面区间的最小值插入当前第一段数组中,就是我们排序开始的位置,把前面区间的最大值插入最后一段数组中,就是排序结束的位置,不过这道题目还有个注意点需要留意;
  8. 后面区间有可能找不到比前面的最大值大的元素,这时候意味着我们后面的部分全部需要排序,那我们的结束部分是整个数组的长度减一。

实现

/**
 * @param {number[]} array
 * @return {number[]}
 */
var subSort = function(array) {
    let prev = Number.MIN_SAFE_INTEGER;
    let prevIndex = 0;
    let splitArr = [];

    // 把数组转换成一个个升序排序的子片段
    for(let i = 0; i < array.length; i++) {
        if (array[i] < prev) {
            splitArr.push(array.slice(prevIndex, i));
            prevIndex = i;
        }

        prev = array[i];
    }

    splitArr.push(array.slice(prevIndex));

    if (splitArr.length < 2) return [-1, -1];

    let maxValue = Number.MIN_SAFE_INTEGER,
        minValue = Number.MAX_SAFE_INTEGER;

    for (let i = 0; i < splitArr.length; i++) {
        // 前面的只需要统计后面的最小值
        if (i > 0) {
           minValue = Math.min(minValue, splitArr[i][0])
        }

        // 后面的只需要统计前面的最大值
        if (i < splitArr.length - 1) {
            maxValue = Math.max(maxValue, splitArr[i][splitArr[i].length - 1]);
        }
    }

    // 开始坐标不需要等于号,越大越好
    let startIndex = splitArr[0].findIndex(item => item > minValue);
    
    // 结束坐标需要等于号,越小越好
    let endIndex = splitArr[splitArr.length - 1].findIndex(item => item >= maxValue);

    // 如果没找到结束坐标,说明后半段都需要排序
    if (endIndex === -1) {
        return [startIndex, array.length - 1];
    }

    return [startIndex, prevIndex + endIndex - 1];
};

结果

image.png

看懂了的小伙伴可以点个关注、咱们下道题目见。如无意外以后文章都会以这种形式,有好的建议欢迎评论区留言。