网络异常,图片无法展示
|
「这是我参与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
,返回最小的操作次数。
这样的一个问题,我们首先可以想到的解题思路可能是使用递归:
- 递归函数传入剩余数值和当前区间,初始传入
target = x,l = 0,r = nums.length-1
。 - 每次减去左边的元素或者右边的元素,并修改对应的
target,l,r
。 - 递归函数的第一个退出条件是
target===0
,此时说明找到一种操作可以将目标值减为0
,如果此时的操作次数小于已记录的做小操作次数,更新最小操作次数。 - 递归函数的另一种退出条件是
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 的最小操作数
如有任何问题或建议,欢迎留言讨论!👏🏻👏🏻👏🏻