[每日一题] 最多能完成排序的块

68 阅读1分钟

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

最多能完成排序的块

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

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

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

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

思路

对于本题

因为本体保证了有n个数,n个数不重不漏全在[0,n1]》对于一个数字k来说,若是想让数组有序,则k的位置必然在第k那么对于一个序列a1,a2,a3,...an来说对于前k个数字来说,其中每一个数字必然满足:max(a1,a2,a3,...,ak)<=k(1)此时,才能够满足前k个数字可以排序为一个升序序列的要求那么对于一个可以排序的序列,若是A序列可以被拆分为A1,A2两个序列由于序列A是满足条件(1)由于A1是序列A的前半段,若是A1满足条件(1),则A2必然满足条件(1)由于我们求的是最多拆分的段,所以,我们就尽可能多的拆分出A2这样的段即:当检查到位置x时,若是x满足条件(1),则证明可以在此处分割一个序列出来因为本体保证了 有 n个数,n个数不重不漏全在[0, n-1]中 \\ 》 对于一个数字 k 来说, 若是想让数组有序,则 k的位置必然在第k位 \\ 那么对于一个序列 a_1, a_2, a_3, ...a_n来说 \\ 对于前k个数字来说,其中每一个数字必然满足: \\ 》 max({a_1, a_2, a_3, ..., a_k}) <= k (1) \\ 此时,才能够满足前k个数字可以排序为一个升序序列的要求 \\ 那么对于一个可以排序的序列,若是 A 序列可以被拆分为 A_1, A_2两个序列 \\ 由于序列A是满足条件(1)的 \\ 由于A_1是序列A的前半段,若是A_1满足条件(1),则A_2必然满足条件(1) \\ 由于我们求的是最多拆分的段,所以,我们就尽可能多的拆分出A_2这样的段 \\ 即: 当检查到位置 x 时,若是 x满足条件(1),则证明可以在此处分割一个序列出来

最后对每一个可以分割的为计数一下即可,时间复杂度 O(n)O(n)

代码

use std::cmp::{max, min};
impl Solution {
    pub fn max_chunks_to_sorted(arr: Vec<i32>) -> i32 {
        let mut cur = -1;
        let mut ans = 0;
        for (i, &x) in arr.iter().enumerate() {
            cur = max(cur, x);
            if cur <= i as i32 {
                ans += 1;
            }
        }
        ans
    }
}

本题拓展

和“最多能完成排序的块”相似,但给定数组中的元素可以重复,输入数组最大长度为20002000,其中的元素最大为10810^8

思路

与上题不同的是,本题的要求中,数字元素可以重复,且元素范围并不在[0, n-1]中,这个就给我们带来了麻烦!

意味着我们并不能像简化版那样去做

真的吗?

我们对原数组进行一个值的离散化处理,把他们映射到[0,n1]我们有n个数,其中有重复的如何处理呢?我们在离散化时,我们相同的元素是紧挨着的于是,我们排完序就得到了一个相对的大小关系的下标值我们就可以把其映射到原数组于是,我们就得到了一个不重不漏的,值在[0,n1]之间的数组!于是,我们就可以利用上题的代码,直接调用得出答案我们对原数组进行一个值的离散化处理,把他们映射到[0, n-1] \\ 我们有n个数,其中有重复的如何处理呢?\\ 我们在离散化时,我们相同的元素是紧挨着的 \\ 于是,我们排完序就得到了一个相对的大小关系的下标值 \\ 我们就可以把其映射到原数组 \\ 于是,我们就得到了一个不重不漏的,值在[0, n-1]之间的数组!\\ 于是,我们就可以利用上题的代码,直接调用得出答案 \\

总体时间复杂度 O(nlogn)O(nlogn)

有没有更快的方式呢?

我想把这个 lognlog n 优化掉

排序是不可能优化了,但是瓶颈也在排序此处!

我们换一种思路:

设原序列为A1A2,A3,A4,...,An现在,每个序列起决定性作用的是序列中最大的元素!所以,我们可以记录一下最大的元素现在新加入一个元素(后面的元素不管!),会发生两种情况:比当前最后一个序列的元素大=>自己就可以成为一个序列比当前最后一个序列的元素小=>则一定会被融合到最后一个序列中但是对于第二种情况我们还需要额外的考虑一点东西那就是我们现在加入这个元素到底应该在哪一个序列?那么,我就必须再去看前面一个序列最大值和这个元素的大小关系,以此类推直到找到一个位置满足max(Ai)<=x<=max(Aj),从Ai+1Aj的序列则被融合为一个序列了! 设原序列为 A_1,A_2, A_3, A_4,...,A_n \\ 现在,每个序列起决定性作用的是序列中最大的元素!\\ 所以,我们可以记录一下最大的元素 \\ 现在新加入一个元素(后面的元素不管!),会发生两种情况:\\ 比当前最后一个序列的元素大 => 自己就可以成为一个序列 \\ 比当前最后一个序列的元素小 => 则一定会被融合到最后一个序列中 \\ 但是对于第二种情况 \\ 我们还需要额外的考虑一点东西\\ 那就是我们现在加入这个元素到底应该在哪一个序列? \\ 那么,我就必须再去看前面一个序列最大值和这个元素的大小关系,以此类推 \\ 直到找到一个位置 满足 max(A_i) <= x <= max(A_j),从 A_i+1 到 A_j的序列则被融合为一个序列了!

这种做法的时间复杂度为 O(N)O(N)

代码

O(Nlog(N))O(Nlog(N))

use std::cmp::{max, min};
impl Solution {
    pub fn max_chunks_to_sorted_help(arr: Vec<i32>) -> i32 {
        let mut cur = -1;
        let mut ans = 0;
        for (i, &x) in arr.iter().enumerate() {
            cur = max(cur, x);
            if cur <= i as i32 {
                ans += 1;
            }
        }
        ans
    }
    pub fn max_chunks_to_sorted(arr: Vec<i32>) -> i32 {
        let mut q: Vec<(i32, usize)> = Vec::new();
        let mut arr = arr;
        for (i, &x) in arr.iter().enumerate() {
            q.push((x, i));
        }
        q.sort();
        for (i, &x) in q.iter().enumerate() {
            arr[x.1] = i as i32;
        }
        return Self::max_chunks_to_sorted_help(arr);
    }
}

O(N)O(N) 利用栈


use std::collections::{VecDeque};
impl Solution {
    pub fn max_chunks_to_sorted(arr: Vec<i32>) -> i32 {
        let mut st = VecDeque::new();
        for v in arr {
            if st.len() == 0 || Some(&v) >= st.back() {
                st.push_back(v);
            } else {
                let now = st.pop_back().unwrap();
                while let Some(&t) = st.back() {
                    match t > v {
                        true => {
                            st.pop_back();
                        }
                        false => break,
                    }
                }
                st.push_back(now);
            }
        }
        return st.len() as i32;
    }
}