每日一题 分割数组

29 阅读1分钟

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

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 进行划分。

解法一: 前后两次遍历

  • 我们可以先从后遍历 目标数组,然后得出每个节点的当前最小值: right
  • 然后我们可以从前遍历目标数组,根据题意left 中的每个元素都小于或等于 right 中的每个元素。
    • 如果当前值 <= right的时候
    • 就是我们需要的结果 返回 i + 1
var partitionDisjoint = function(nums) {
    const n = nums.length;
    const min = new Array(n).fill(nums[n - 1]);
    // left 和 right 都是非空的 所以right必定有值
    // 我们需要比对 i+1 所以从倒数第二项开始
    for (let i = n - 2; i >= 0; i--) {
        min [i] = Math.min(nums[i], min[i + 1]);
    }

    let max = 0;
    for (let i = 0; i < n - 1; i++) {
        max = Math.max(max, nums[i]);
        if (max <= min[i + 1]) {
            return i + 1;
        }
    }
    return n - 1;
};

解法二:一次遍历

  • 假设 我们已经有了分割点 left
  • 那么我们可以得出 这个分割节点 中nums[0, left]的最大值:leftMax
  • 又因为 left 中的每个元素都小于或等于 right 中的每个元素。
  • 所以我们可以得到以下的结论:
    • 如果 nums[i] < leftMax
    • 那么就说明当前的节点不能在 right 中
    • 这个时候 left 就要右移 left = i
    • leftMax 就要进行重新维护为 nums[0, left]的最大值
    • 最终的值 就是 left + 1
var partitionDisjoint = function(nums) {
  let left = 0
  let leftMax = nums [0]
  let currMax = nums[0] // 每次移动期间的最大值
  // nums.length - 1 是为了保证 left 和 right 都是非空的
  for(let i = 0; i< nums.length - 1; i++) {
    currMax = Math.max(currMax, nums[i])
    if(nums[i] < leftMax){
      left = i
      leftMax = currMax
    }
  }
  return left + 1
};