算法--分割数组

87 阅读1分钟

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

题目

leetcode 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 <= 105

0 <= nums[i] <= 106

可以保证至少有一种方法能够按题目所描述的那样对 nums 进行划分。

题解

划分后的两个子数组要满足题目要求,需要保证“数组前缀最大值”小于等于“数组后缀最小值”。

因此,我们可以先预处理出数组的后缀最小值,记录在 mi 数组中。

然后从前往后遍历数组,维护数组前缀的最大值 mx,当遍历到某个位置时,如果数组前缀最大值小于等于数组后缀最小值,那么当前位置就是划分的分界点,直接返回即可。

/**
 * @param {number[]} nums
 * @return {number}
 */
var partitionDisjoint = function(nums) {//离散化+排序
    let arr = nums.map((val,index)=>({val,index}));
    arr.sort((a,b)=>a.val-b.val);
    let i = 0,max=-1;
    for(;i<arr.length;i++){
        max =Math.max(max,arr[i].index);
        if(max === i) break;
    }
    return max+1;
}
var partitionDisjoint = function(nums) {//双指针
    let left = 0,right = nums.length-1;
    let leftMax = [nums[0]],rightMin = [];
    rightMin[right] = nums[right];
    while(left<right){
        if(leftMax[left]<=rightMin[right]){
            right--;
            rightMin[right] = Math.min(rightMin[right+1],nums[right]);
        }else{
            left++;
            leftMax[left] = Math.max(leftMax[left-1],nums[left]);
        }
    }
    //console.log(left,right,leftMax,rightMin);
    while(leftMax[left]>rightMin[++right]){
        left++;
        leftMax[left] = Math.max(leftMax[left-1],nums[left]);
    }
    return left+1;
};

代码详解

minRight[i]储存的是nums数组当前下标nums[i]到数组尾nums[numsSize-1]这一段的最小值。因为范围是i到numsSize-1,所以从数组尾开始倒序循环。这是第一次遍历,目的是更新minRight数组的值。

第二次遍历则是不断更新maxLeft,并与minRight[i]比较。maxLeft存储了nums[0]到当前下标的nums[i]的最大值。 在第二次循环中,i作为left与right两个数组的分界点,nums[0]到nums[i]为left,nums[i+1]到nums[numsSize-1]则是right,maxLeft与minRIght[i+1]相比,即是left的最大值与right的最小值相比。