leetcode 915. 分割数组

304 阅读1分钟

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

915. 分割数组

给定一个数组 nums ,将其划分为两个连续子数组 left 和 right, 使得:

  • left 中的每个元素都小于或等于 right 中的每个元素。
  • left 和 right 都是非空的。
  • left 的长度要尽可能小。

在完成这样的分组后返回 left 的 长度

用例可以保证存在这样的划分方法。

用例1

输入: nums = [5,0,3,8,6]
输出: 3
解释: left = [5,0,3]right = [8,6]

用例2

输入: nums = [1,1,1,0,6,12]
输出: 4
解释: left = [1,1,1,0]right = [6,12]

数据范围

  • 2 <= nums.length <= 10^5
  • 0 <= nums[i] <= 10^6

思路

题目要求的是分割为两个数组,前半部分的最大值小于等于后半部分的最大值: 就是:

对于1n来说:maxi=1ka[i]mini=k+1na[i]对于 1-n来说: \max_{i=1}^k a[i] \leq \min_{i=k+1}^n a[i]

要满足这个关系式意味着需要去计算两个区间的最值。

我们的划分点有 nn个划分点,意味着我们需要计算nn次最大值最小值,时间复杂度总体上是O(n2)O(n^2)

得想办法解决时间复杂度的问题:

这里给几种做法,思想都是基于优化区间查询时间的做法:

做法1:线段树线段树

线段树可以迅速求出一段区间的最大值和最小值,单次时间复杂度为lognlogn,n次查询最大值最小值,总体时间复杂度为O(nlogn)O(nlogn)

做法2:树状数组树状数组

树状数组也可以迅速求出一段区间的最大值和最小值,并且比线段树好写不少,时间复杂度和线段树类似,总体O(nlogn)O(nlogn)

做法3:STST表

ST表是一种高效的离线区间查询算法,和线段树、树状数组不同的是,ST表是基于倍增实现的一种区间查询最值查询算法。一般得要求区间具有重叠性 总体时间复杂度O(nlogn)O(nlogn)

做法4:后缀min数组后缀min数组

前面三种做法都是通用的做法,对于本题来说,可以更特殊一点,不用那么麻烦,因为第二个数组一定是从尾部到中间某个位置k的,这就导致了区间是只扩充不缩小的。

这个就给我们带来了很多便利。

因为有这样的性质:

maxi=xn=max(x,maxi=x+1n) \max_{i=x}^n = \max(x, \max_{i=x+1}^n)

所以,我们求一个后缀的min数组,就可以实现O(1)O(1)转移最大值了

同类前面所有的最大值也算一样的操作计算即可

use std::cmp::{max, min};
impl Solution {
    pub fn partition_disjoint(nums: Vec<i32>) -> i32 {
        let n = nums.len();
        let mut mi = vec![0; n];
        for i in (0..n).rev() {
            if i == n - 1 {
                mi[i] = nums[i]
            } else {
                mi[i] = min(mi[i + 1], nums[i])
            }
        }
        let mut ma = nums[0];
        for i in 0..n - 1 {
            ma = max(ma, nums[i]);
            if ma <= mi[i + 1] {
                return (i+1) as i32;
            }
        }
        -1
    }
}