【C/C++】769. 最多能完成排序的块

287 阅读4分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第24天,点击查看活动详情


题目链接:769. 最多能完成排序的块

题目描述

给定一个长度为 n 的整数数组 arr ,它表示在 [0, n - 1] 范围内的整数的排列。

我们将 arr 分割成若干 (即分区),并对每个块单独排序。将它们连接起来后,使得连接的结果和按升序排序后的原数组相同。

返回数组能分成的最多块数量。

提示:

  • n == arr.length
  • 1n101 \leqslant n \leqslant 10
  • 0arr[i]<n0 \leqslant arr[i] < n
  • arr 中每个元素都 不同

示例 1:

输入: arr = [4,3,2,1,0]
输出: 1
解释:
将数组分成2块或者更多块,都无法得到所需的结果。
例如,分成 [4, 3], [2, 1, 0] 的结果是 [3, 4, 0, 1, 2],这不是有序的数组。

示例 2:

输入: arr = [1,0,2,3,4]
输出: 4
解释:
我们可以把它分成两块,例如 [1, 0], [2, 3, 4]。
然而,分成 [1, 0], [2], [3], [4] 可以得到最多的块数。

整理题意

题目要求首先对给定数组进行分块,块需要是连续的数组元素,然后对每一块进行排序,使得块排序后的数组成为升序数组(因为数组中每个元素不同),问最多能够分成多少块。

解题思路分析

注意题目的数据范围,数组中的元素范围在 0arr[i]<n0 \leqslant arr[i] < n 且每个元素互不相同,所以排序后的数组下标对应数组元素。

思维 + 暴力

  • 当遍历到第 i 个位置时,如果可以切分为块,那前 i 个位置的最大值一定等于 i;否则,一定有比 i 小的数划分到后面的块,那块排序后,一定不满足升序。
  • 如果当前下标 i(包括 i )之前的最大值为 i,则说明可以切分为块;否则前半部分有大于 i 的元素,后半部分有小于 i 的元素,块排序后必不满足题意。

单调栈

该题还可以使用单调栈的解法,不过在空间复杂度上是不如 贪心思维 的,不过当数组中存在重复元素时是无法使用这种贪心思维的,会导致下标错位。

具体实现

思维 + 暴力

从头到尾遍历数组,记录前缀中的最大元素值,如果当前下标等于前缀中的最大值,那么就可以将此处切一刀进行分块,记录块的数量。然后依次直至遍历完数组,输出块的数量。

单调栈

单调栈的解法为:

  • 始终保持栈栈底为较小元素,栈顶为较大元素;
  • 从头到尾遍历数组,如果当前元素小于栈顶元素,那么说明当前元素是和栈顶(前一块)是同一块的;
  • 记录栈顶最大的元素值,然后一直弹出栈顶元素,直至栈为空或者大于栈顶元素。
  • 然后将刚刚记录的最大元素压入栈顶,用这个最大元素表示当前这个块,这个过程可以看作把当前元素融入前面的块,同时把前面的块融为一块。
  • 重复这个过程,最后栈中的元素即为每个块中的最大元素,输出栈中的元素个数即为最大可拆分的块数量。

复杂度分析

  • 时间复杂度:O(n)O(n),其中 n 为数组 arr 的长度。使用 思维 + 暴力单调栈 的方法时间复杂度都为 O(n)O(n)
  • 空间复杂度:使用 思维 + 暴力 的方法时间复杂度为 O(1)O(1),不计返回值的空间;使用 单调栈 的方法由于使用到了栈,最坏的情况下需要 O(n)O(n) 的栈空间。

代码实现

思维 + 暴力

class Solution {
public:
    int maxChunksToSorted(vector<int>& arr) {
        // n 为数组大小,m 为前缀中的最大值,ans 为分块数量
        int n = arr.size(), m = 0, ans = 0;
        for(int i = 0; i < n; i++){
            // 维护最大值 m
            m = max(m, arr[i]);
            // 当最大值等于下标时可以分块
            if(m == i) ans++;
        }
        return ans;
    }
};

单调栈

class Solution {
public:
    int maxChunksToSorted(vector<int>& arr) {
        // 单调栈
        stack<int> s; while(s.size()) s.pop();
        for(int &num : arr){
            // 因为 arr 中每个元素都不同,可以省略等号
            // s.top() < num
            if(s.empty() || s.top() <= num) s.push(num);
            else{
                // m 记录当前块最大值
                int m = s.top();
                while(!s.empty() && s.top() > num){
                    m = max(m, s.top());
                    s.pop();
                }
                s.push(m);
            }
        }
        return s.size();
    }
};

总结

  • 该题的贪心思维是比较巧妙的,但是这是建立在题目的种种条件下的特殊情况,因为数组元素不重复且数组元素范围为 [0, n - 1];而单调栈的解法较为常规,并且可以处理数组中存在重复元素的情况。
  • 测试结果:

769. 最多能完成排序的块.png

769. 最多能完成排序的块(单调栈).png

结束语

成功往往在多次失败之后才姗姗来迟,每一次跌倒后再爬起来,都能使你本领更强大、信心更充足。永不放弃,持之以恒,将挫折变成你进步的阶梯,你就获得了成功的机会。新的一天,加油!