「这是我参与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 <= 1051 <= nums[i] <= 1041 <= 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 的最小操作数
如有任何问题或建议,欢迎留言讨论!👏🏻👏🏻👏🏻