1. 题面
给定一个数组 nums
,将其划分为两个连续子数组left
和right
, 使得:
left 中的每个元素都小于或等于 right 中的每个元素。left
和 right
都是非空的。left
的长度要尽可能小。
在完成这样的分组后返回left
的 长度 。
用例可以保证存在这样的划分方法。
来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/partition-array-into-disjoint-intervals
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
示例 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 <= 105
0 <= nums[i] <= 106
可以保证至少有一种方法能够按题目所描述的那样对 nums 进行划分。
2. 题目分析
题目的要求是从nums
中找到一个i
,使得nums[i]
左边的元素全部要小于等于右边的元素。
那么从可能性上来讲从0~n-1
中的每个元素都有可能是这个分割点,所以我们只需要枚举这个分割点即可。
由于题目说了肯定能够找到一种划分方法,使得left
和right
数组不为null
这就是基本思路,我们只需要枚举每个分割点的位置,然后进行
验证
即可。
现在的问题是如何验证得高效?目前已经知道题目的length
是10的5次方,如果按照常规思路去验证的话(从分割点从左出发,向右出发,肯定是n次的访问)
,最终就是n方
的复杂度,肯定是通过不了这个题目的。
但是如果我们能够通过提前知道[0,i]
的最大值,(i,n-1]
的最小值,然后判断是从最大值是小于最小值的,这样的话就能够快速判断了。
那么如何来实现这个事情呢?
[0,i]
区间内的最大值如何求解?
很简单,一次遍历即可,从0开始遍历,我们定义一个
preMax[]
,用来记录每一个i
的位置下的最大值。
preMax[0] = nums[0];//base-case
for(int i = 1;i<n;i++){
preMax[i] = Math.max(preMax[i-1],nums[i]);
}
[i,n-1]
区间内的最小值如何求解?
与求最大值类似,只不过遍历方向变了而已,看代码
suffixMin[n-1] = nums[n-1];
for(int i = n-2;i>=0;i--){
suffixMin[i] = Math.min(suffixMin[i+1],nums[i]);
}
以上的代码可以合并为一段
int n = nums.length;
int[][] pre = new int[n+1][2];
//pre[i][0]:是[0,i]的最大值
//pre[i][1]:是[i,n-1]的最小值
pre[0][0] = nums[0];
pre[n-1][1] = nums[n-1];
//枚举left数组
for(int i = 1;i<=n-1;i++){
pre[i][0] = Math.max(pre[i-1][0],nums[i]);
if(i>=2){
//就执行这个
System.out.println(pre[n-i+1][1]+":"+nums[n-i]);
pre[n-i][1] = Math.min(pre[n-i+1][1],nums[n-i]);
}
}
//边界:n-i
//i==n-1,此时j=n-(n-1)=n-n+1=1
//还差个0
pre[0][1] =Math.min(pre[1][1],nums[0]);
3. 题解代码
class Solution {
public int partitionDisjoint(int[] nums) {
int n = nums.length;
int[][] pre = new int[n+1][2];
//pre[i][0]:是[0,i]的最大值
//pre[i][1]:是[i,n-1]的最小值
pre[0][0] = nums[0];
pre[n-1][1] = nums[n-1];
//枚举left数组
for(int i = 1;i<=n-1;i++){
pre[i][0] = Math.max(pre[i-1][0],nums[i]);
if(i>=2){
//System.out.println(pre[n-i+1][1]+":"+nums[n-i]);
pre[n-i][1] = Math.min(pre[n-i+1][1],nums[n-i]);
}
}
//边界:n-i
//i==n-1,此时j=n-(n-1)=n-n+1=1
//还差个0
pre[0][1] =Math.min(pre[1][1],nums[0]);
//然后开始枚举分界点
int left = Integer.MIN_VALUE;
for(int i = 0;i<n-1;i++){
//左边数组的最大值要小于等于右边数组的最小值
//System.out.println(pre[i][0]+" "+pre[i+1][1]);
if(pre[i][0] <= pre[i+1][1]){
//结算此时的i
left = i+1;
break;
}
}
return left;
}
}