769. 最多能完成排序的块

思路

可以发现相邻两个块具有如下性质:前面一个块的「最大值」一定小于后一个块的「最小值」。

亲测,后面两个方法能通过768题。768. 最多能完成排序的块 II


贪心(占位置)

该方法只适用于数组元素值为0n-1的情况。

如果数组内元素值可以相同,且不在这个范围内,则该方法失效,如题:768. 最多能完成排序的块 II。可使用后面两种方法。

由题意可知,长度为n的数组内元素值的是[0,n-1]范围内的整数的一个排列。那么显然,该数组arr排序后必有arr[i] = i

因此,我们可以进行下述的思考,遍历到下标i的元素arr[i]时,若有:

  • arr[i] < i,则说明这个元素应该放在前面,显然下标i之前的元素排序后arr[i]能够放在正确的位置上;
  • arr[i] == i,则说明该元素已经在正确的位置上了;
  • arr[i] > i,说明这个元素应该放在后面,下标i之前的元素排序后arr[i]仍无法放置在正确的位置上,至少需要遍历到下标i = arr[i]处。

可以利用一个变量righ表示:当前块区间右边界下标至少为right

当遍历到下标i==right时,该块内元素排序后一定等价于原数组排序后该块区间内的值的。


代码如下:

class Solution {
public:
    int maxChunksToSorted(vector<int>& arr) {
        auto n = arr.size();
        int ans = 0;
        int right = 0;  //表示当前块的右边界

        for (int i = 0; i < n; ++i) {
            //对于arr[i],它真实的位置应该在 idx = arr[i] 上。
            right = max(right, arr[i]);
            if (i == right){
                ans++;
                right++;
            }
        }

        return ans;
    }
};


模拟

由上一节分析可知,贪心的方法只适用于数组元素值为0n-1的情况。

当数组内元素可重复且元素组任意时如何解决呢?

Solution769Image01.jpg

考虑块已经分好的情况,相邻的两个块chunk[x]以及chunk[x+1]一定是满足以下条件的:

max ⁡ { c h u n k [ x ] } < m i n { c h u n k [ x + 1 ] } \max \{ chunk[x] \} < min \{ chunk[x+1] \} max{chunk[x]}<min{chunk[x+1]}

那么在块还没有确定下来的情况时,应该如何处理?假设前面的块已经确定了,此时当前块chunk[x]的左边界下标为curIdx,但是右边界nextIdx-1还没有确定(nextIdx为下一个块的起始位置)。下面对如何确定nextIdx进行分析:

  1. 初始化时,可以令nextIdx = curIdx+1,此时chunk[x]是一个长度为1的块。
  2. 初始化结束后,我们需要去寻找这样一个下标nextIdx:区间[nextIdx, n-1]内的元素最小值是大于区间[curIdx, nextIdx-1]内元素的最大值的。显然,我们需要对区间[curIdx, n-1]进行一次遍历。
  3. 遍历到下标j时,可能有以下情况:
    • arr[j] < max{chunk[x]},显然arr[j]也是属于块chunk[x]内的,因此区间右边界可以进行拓展:nextIdx = j+1
    • arr[j] >= max{chunk[x]},此时arr[j]可能是属于下一个块的,因此不进行扩展。

注意到,这里每次扩展后max{chunk[x]}都可能发生改变,如果每次去遍历区间[curIdx, nextIdx-1]去得到最大值会十分费时。可以利用两个变量进行记录:

  1. curMaxValue表示区间[curIdx, nextIdx-1],即当前块内的最大值;
  2. maxValue表示区间[curIdx, j]内的最大值。

显然maxValue >= curMaxValue,一旦chunk[x]区间进行了扩展,令curMaxValue = maxValue即可。

  1. 遍历结束后,当前块chunk[x]的右边界就确定了下来,块数加1,令curIdx = nextIdx进行下一轮循环即可。

代码如下:

class Solution {
public:
    int maxChunksToSorted(vector<int>& arr) {
        auto n = arr.size();
        int ans = 0;

        int curIdx = 0, nextIdx, j;
        int maxValue;  //已遍历区间内的最大值
        int curMaxValue;  // 当前块的最大值

        while(curIdx < n){
            nextIdx = curIdx + 1;
            j = nextIdx;  //arr[curIdx...nextIdx-1]为当前块
            curMaxValue = maxValue = arr[curIdx];
            
            while(j<n){  //向右拓展边界
                if (arr[j] < curMaxValue) {
                    nextIdx = j+1;
                    curMaxValue = maxValue;
                }
                maxValue = max(maxValue, arr[j])
                j++;
            }
            ans++;

            curIdx = nextIdx;
        }
        return ans;
    }
};

时间复杂度为 O ( n 2 ) O(n^2) O(n2)


单调栈

是否存在时间复杂度为 O ( n ) O(n) O(n)的方法?可以利用单调栈来实现。

对于arr[0]...arr[i],假设其分块方式为chunk[0],chunk[1],...,chunk[x]。此时新加入一个数arr[i+1],若有:

  • arr[i+1] >= max{chunk[x]},则说明arr[i+1]可以自成一个块chunk[x+1]
  • arr[i+1] < max{chunk[x]},则arr[i+1]应合并进入chunk[x],合并后还没有结束,还需要继续向前进行判断,直至arr[i+1] >= max{chunk[y]},此时chunk[y],chunk[y+1],...,chunk[x], arr[i+1]合并为新的chunk[y+1],并且此时有max{chunk[y+1]} = max{chunk[x]}

每次遍历到一个新的元素都是需要从后往前判断,并将合并后的结果加在最后,与栈的操作相同。可以利用单调栈来实现,栈中元素为每个块的最大值。

遍历结束后,栈中元素的数量即为答案。

代码如下:

class Solution {
public:
    int maxChunksToSorted(vector<int>& arr) {
        auto n = arr.size();
        int stk[n];
        int top = -1;  //栈顶下标

        for (const auto &item : arr){
            if (top < 0 || item >= stk[top]){
                stk[++top] = item;
            } else {
                int maxValue = stk[top];
                while (top >= 0 && stk[top] > item){
                    top--;
                }
                stk[++top] = maxValue;
            }
        }

        return top+1;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值