算法:无序数组中需要排序的最短子数组长度

题目来源

题目描述

给定一个无序数组arr,求出需要排序的最短子数组的长度,对子数组排序后能使得整个数组有序,即为需要排序的数组。例如:arr=[1,5,3,4,2,6,7]返回4,因为只有[5,3,4,2]需要排序。

题目解析

题意分析

在这里插入图片描述

因此,我们只需要找到左边第一个无序元素,右边第一个无序元素即可

分析

为了容易理解,我们从O(nlogn)出发来推导O(n)方案
方案一:O(nlogn)方案

  • 把整个序列重新排序一下。显然的,只有挪窝的数字才需要排序。
    1 5 3 4 2 6 7
    1 2 3 4 5 6 7
  • 由于只能连续子数组一同排序,所以答案是以最左、左右需要挪窝数字为左右端点的数组。

方案二:O(n)

  • 方案一有一个问题,就是我们引入了排序却没有用到相对次序,这可能会浪费了算力。
    那么有没有办法避免排序呢?
  • 有的,对于第i个元素,如果它满足下列公式,那么它显然不必要挪窝嘛。
    max(arr[0…i)<=arr[i]<=max(arr[i…n])
  • 换过来说,如果需要挪窝,那么显然不满足这个条件不是?

so,两个for解决

思路

  • 从左往右遍历,维护一个max,为划过部分的最大值
    • 初始时max = arr[0],i从1开始遍历
    • arr[i] > max,(破坏单调性),×,当前值需要移动到前面去
    • arr[i] <= max,(单调性不变),√
    • 并记录最右边画X的位置,这个位置就是要排序的右边界
  • 从右往左遍历,维护一个min,为划过部分的最小值
    • 初始时max = arr[n-1],i从n-2开始遍历
    • arr[i] < max,(破坏单调性),×,当前值需要移动到后面去
    • arr[i] >= max,(单调性不变),√
    • 并记录最左边画X的位置,这个位置就是要排序的左边界

举个例子

在这里插入图片描述

  • 从左到右遍历:

在这里插入图片描述
在这里插入图片描述

  • 从右到左遍历

在这里插入图片描述
在这里插入图片描述
因此,需要排序的个数是:
在这里插入图片描述

问题:只进行一趟排序不行吗?为什么一定要两边排序

我们举个反例

在这里插入图片描述
如果只一趟排序,那么8没有人管

实现


int getMinLength1(std::vector<int> arr){
    int N = arr.size();
    if(N <= 1){
        return 0;
    }

    int lmax = arr[0];
    int idx1 = -1;
    for (int i = 1; i < N; ++i) {
        if(lmax > arr[i]){
            idx1 = i;
        }else{
            lmax = arr[i];
        }
    }

    if(idx1 == -1){
        return 0;
    }

    int rmin = arr[N - 1];
    int idx2 = -1;
    for (int i = N - 2; i >= 0; --i) {
        if(rmin < arr[i]){
            idx2 = i;
        }else{
            rmin = arr[i];
        }
    }

    return idx1 - idx2 + 1;
}

对数器


int getMinLength2(std::vector<int> arr){
    int N = arr.size();
    if(N <= 1){
        return 0;
    }


    int max = arr[0];
    int nomaxIndex = -1;
    for (int i = 1; i != N; ++i) {
        if(arr[i] < max){
            nomaxIndex = i;
        }else{
            max = std::max(max, arr[i]);
        }
    }


    if(nomaxIndex == -1){
        return 0;
    }

    int min = arr[N - 1];
    int nominIndex = -1;
    for (int i = N - 2; i != -1; --i) {
        if(min < arr[i]){
            nominIndex = i;
        }else{
            min = std::min(min, arr[i]);
        }
    }


    return nomaxIndex - nominIndex + 1;
}

std::default_random_engine e;
void generateRandom(int maxLen, int minValue, int maxValue, std::vector<int> &arr){
    std::uniform_int_distribution<int> distS(1, maxLen);
    std::uniform_int_distribution<int> distV(minValue, maxValue);
    std::uniform_real_distribution<double> distf;
    int size = distS(e);
    arr.resize(size);
    for (int i = 0; i < size; ++i) {
        arr[i] = distV(e);
    }
}

int main() {
    e.seed(time(NULL));
    int maxLen = 100, minValue = -1000, maxValue = 100000;
    for (int i = 0; i < 100000; ++i) {
        std::vector<int> arr;
        generateRandom(maxLen, minValue, maxValue, arr);
        if(getMinLength1(arr) != getMinLength2(arr)){
            printf("error\n");
            return 0;
        }
    }
    printf("ok\n");
    return 0;
}


  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值