[路飞]_将 x 减到 0 的最小操作数

169 阅读2分钟

「这是我参与2022首次更文挑战的第11天,活动详情查看:2022首次更文挑战

1658. 将 x 减到 0 的最小操作数

题目

给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。

如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。

示例1

输入: nums = [1,1,4,2,3], x = 5
输出: 2
解释: 最佳解决方案是移除后两个元素,将 x 减到 0 。

示例2

输入: nums = [5,6,7,8,9], x = 4
输出: -1

示例3

输入:nums = [3,2,20,1,1,3], x = 10
输出:5
解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。

题解

前缀和 + 哈希表记录长度

每次只能移除数组最左或者最右;最容易想到的是通过前缀和;左右前缀和相加等于x,将此时左右前缀长度记录下来取最小值

分析示例:nums = [3,2,20,1,1,3], x = 10

  • 左侧前缀和 left = [3,5,25,26,27,30]
  • 右侧前缀和 right = [30,27,25,5,4,3]
  • 从left左侧,right从右侧逐一搜索查找left[l]+right[r]===xleft[l] + right[r] === x
  • 理论上没什么问题,但是逐一搜索left[l]+right[r]===xleft[l] + right[r] === x,时间复杂度太高,可以将前缀和放在leftMap、rightMap中,
  • map中记录这个得到这个前缀和需要的长度
  • 在搜索left[l]+right[r]===xleft[l] + right[r] === x时,可以通过xleft[l]=kx - left[l] = k , k 在rightMap是否存在降低时间复杂度
  • 结果取从left搜索得到最小长度与从right搜索得到的最小长度的较小值
  • 注意边界处理,

根据上述思路编辑代码如下

代码

var minOperations = function (nums, x) {
  const len = nums.length
  const leftMap = {}
  const rightMap = {}
  let left = 0
  let right = 0
  for (let i = 0; i < len; i++) {
    left += nums[i]
    if (leftMap[left] === undefined) {
      leftMap[left] = i + 1
    }

    right += nums[len - 1 - i]
    if (rightMap[right] === undefined) {
      rightMap[right] = i + 1
    }
  }

  let leftNum = 0
  let leftMin = len+1
  for (let i = 0; i < len; i++) {
    leftNum += nums[i]
    const diff = x - leftNum
    if (diff === 0) {
      leftMin = Math.min(leftMap[leftNum], leftMin)
    }
    if (diff > 0 && rightMap[diff] !== undefined) {
      if (leftMap[leftNum] + rightMap[diff] <= len) {
        leftMin = Math.min(leftMap[leftNum] + rightMap[diff], leftMin)
      }
    }
  }

  let rightNum = 0
  let rightMin = len+1
  for (let i = len - 1; i >= 0; i--) {
    rightNum += nums[i]
    const diff = x - rightNum
    if (diff === 0) {
      rightMin = Math.min(rightMin, rightMap[rightNum])
    }
    if (diff > 0 && leftMap[diff] !== undefined) {
      if (rightMap[rightNum] + leftMap[diff] <= len) {
        rightMin = Math.min(rightMin, rightMap[rightNum] + leftMap[diff])
      }
    }
  }
  if (rightMin === len+1 && leftMin === len+1) return -1
  if (rightMin === len+1) return leftMin
  if (leftMin === len+1) return rightMin
  return Math.min(leftMin, rightMin)
}

滑动窗口

对于数组 nums=[i0,i1,ij,...,ik,in1,in]nums = [i_0,i_1,i_j,...,i_k,i_{n-1},i_n]

如果数组 numsnums 总和为 totaltotal

一定有

nums[i0]+...+nums[ij1]+nums[ik+1]+...+nums[in]=xnums[i_0] + ... + nums[i_{j-1}] + nums[i_{k+1}] + ... + nums[i_n] = x

等价于

totalx=nums[ij]+...+nums[ik]total - x = nums[i_j] + ... + nums[i_k]

现在将问题转换为求 numsnums 数组连续子数组之和等于 totalxtotal - x 的最大长度

根据上述思路编辑代码如下:

代码

var minOperations = function (nums, x) {
  const len = nums.length
  const total = nums.reduce((a, b) => a + b)
  //console.log('total', total)
  if (total < x) return -1
  if (total === x) return len

  const target = total - x
  let current = 0
  let max = 0
  let result = 0
  for (let i = 0; i < len; i++) {
    max += 1
    current += nums[i]
    while (current > target) {
      current -= nums[i + 1 - max]
      max -= 1
    }
    if (current === target) {
      result = Math.max(result, max)
    }
  }
  return result === 0 ? -1 : len - result
}