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

简介: 1658-将 x 减到 0 的最小操作数

网络异常,图片无法展示
|


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


[题目地址]


给你一个整数数组 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 。
复制代码


提示:


  • 1 <= nums.length <= 105
  • 1 <= nums[i] <= 104
  • 1 <= x <= 109


递归求解,超时


解题思路


本题题意并不复杂,要求我们每次可以在整数数组的边缘删除一个元素,并在目标值上减去响应的数字,如果刚好可以把目标值减为 0,返回最小的操作次数。


这样的一个问题,我们首先可以想到的解题思路可能是使用递归:


  1. 递归函数传入剩余数值和当前区间,初始传入 target = x,l = 0,r = nums.length-1
  2. 每次减去左边的元素或者右边的元素,并修改对应的 target,l,r
  3. 递归函数的第一个退出条件是 target===0,此时说明找到一种操作可以将目标值减为 0,如果此时的操作次数小于已记录的做小操作次数,更新最小操作次数。
  4. 递归函数的另一种退出条件是 l>r || target<0 || 此时的操作次数大于等于已记录的最小操作次数,这个时候说明继续操作是没有意义的,退出递归。


代码实现


var minOperations = function (nums, x) {
  /**
   * 递归函数
   * @param {number[]} target 剩余要减去的数值
   * @param {number} l 未操作区间的起始下标
   * @param {number} r 未操作区间的结束下标
   * @return  void
   */
  function calc(target, l, r) {
    if (target === 0 && l - 0 + len - r - 1 < res) {
      res = l - 0 + len - r - 1
      return
    }
    if (l > r || target < 0 || l - 0 + len - r - 1 >= res) {
      return
    }
    calc(target - nums[l], l + 1, r)
    calc(target - nums[r], l, r - 1)
  }
  // 获取输入数组长度
  const len = nums.length
  // 初始化结果值为数组长度+1,即一个非法值
  let res = len + 1
  // 调用递归函数
  calc(x, 0, len - 1)
  // 如果结果值合法,返回结果值,否则返回 -1
  return res > len ? -1 : res
}
复制代码


但是以上算法的时间复杂度为 O(n²),没有达到本题的要求,提交会超时。


反向思维,滑动窗口


解题思路


递归解题超时了,此时我们换一种思路,既然本题要求每次只能在数组的边缘删除元素,那么剩余的元素必然是在数组的 “中间的”,也就是说它们必然是连续的,那么我们只要找到一个连续子数组,其中元素的和值等于输入数组的元素和值减去目标值即可。


代码实现


var minOperations = function (nums, x) {
  // 获取输入数组的长度
  const len = nums.length
  // 获取输入数组中整数的和值
  let total = 0
  for (let i = 0; i < len; i++) total += nums[i]
  // 如果目标值大于和值,说明不可能减到 0,返回 -1
  if (total < x) return -1
  // 如果目标值等于和值,说明需要把数组中所有元素都减去,返回数组长度
  if (total === x) return len
  // 初始化结果值为数组长度+1,即一个非法值
  let res = len + 1,
    // 区间起始下标为0
    l = 0,
    // 区间结束下标为0
    r = 0
  // 区间整数和值为0
  sum = 0
  // 计算数组中整数和值与目标值的差值 => 目标区间中元素的和值
  const diff = total - x
  // 当区间结束下标没有走到数组末尾
  while (r < len) {
    // 累加和值
    sum += nums[r]
    // 如果区间中和值大于差值,则应该把区间最左侧的整数进行删除
    while (sum > diff) {
      sum -= nums[l]
      l++
    }
    // 如果区间和值等于差值,说明找到了一种操作方式,尝试更新最小操作次数
    if (sum === diff) res = Math.min(res, len - r + l - 1)
    r++
  }
  // 如果结果值合法,返回结果值,否则返回 -1
  return res > len ? -1 : res
}
复制代码


以上算法的时间复杂度为 O(n),提交后击败 90+% 的用户。


至此我们就完成了 1658-将 x 减到 0 的最小操作数


如有任何问题或建议,欢迎留言讨论!👏🏻👏🏻👏🏻

相关文章