|
| 1 | +# 题目地址(1658. 将 x 减到 0 的最小操作数) |
| 2 | + |
| 3 | +https://leetcode-cn.com/problems/minimum-operations-to-reduce-x-to-zero |
| 4 | + |
| 5 | +## 题目描述 |
| 6 | + |
| 7 | +``` |
| 8 | +给你一个整数数组 nums 和一个整数 x 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 x 中减去该元素的值。请注意,需要 修改 数组以供接下来的操作使用。 |
| 9 | +
|
| 10 | +如果可以将 x 恰好 减到 0 ,返回 最小操作数 ;否则,返回 -1 。 |
| 11 | +
|
| 12 | + |
| 13 | +
|
| 14 | +示例 1: |
| 15 | +
|
| 16 | +输入:nums = [1,1,4,2,3], x = 5 |
| 17 | +输出:2 |
| 18 | +解释:最佳解决方案是移除后两个元素,将 x 减到 0 。 |
| 19 | +示例 2: |
| 20 | +
|
| 21 | +输入:nums = [5,6,7,8,9], x = 4 |
| 22 | +输出:-1 |
| 23 | +示例 3: |
| 24 | +
|
| 25 | +输入:nums = [3,2,20,1,1,3], x = 10 |
| 26 | +输出:5 |
| 27 | +解释:最佳解决方案是移除后三个元素和前两个元素(总共 5 次操作),将 x 减到 0 。 |
| 28 | + |
| 29 | +
|
| 30 | +提示: |
| 31 | +
|
| 32 | +1 <= nums.length <= 10^5 |
| 33 | +1 <= nums[i] <= 104 |
| 34 | +1 <= x <= 109 |
| 35 | +
|
| 36 | +``` |
| 37 | + |
| 38 | +## 前置知识 |
| 39 | + |
| 40 | +- 堆 |
| 41 | +- [滑动窗口](../thinkings/slide-window.md) |
| 42 | + |
| 43 | +## 公司 |
| 44 | + |
| 45 | +- 暂无 |
| 46 | + |
| 47 | +## 堆 |
| 48 | + |
| 49 | +### 思路 |
| 50 | + |
| 51 | +这里可以使用堆来解决。具体来说是我自己总结的**多路归并**题型。 |
| 52 | + |
| 53 | +> 关于这个算法套路,请期待后续的堆专题。 |
| 54 | +
|
| 55 | +### 代码 |
| 56 | + |
| 57 | +代码支持:Python3 |
| 58 | + |
| 59 | +Python3 Code: |
| 60 | + |
| 61 | +```py |
| 62 | +class Solution: |
| 63 | + def minOperations(self, nums: List[int], x: int) -> int: |
| 64 | + # 看数据范围,这种方法铁定超时(指数复杂度) |
| 65 | + h = [(0, 0, len(nums) - 1, x)] |
| 66 | + while h: |
| 67 | + moves,l,r,remain = heapq.heappop(h) |
| 68 | + if remain == 0: return moves |
| 69 | + if l + 1 < len(nums): heapq.heappush(h, (moves + 1, l + 1,r, remain-nums[l])) |
| 70 | + if r > 0: heapq.heappush(h, (moves + 1, l,r-1, remain-nums[r])) |
| 71 | + return -1 |
| 72 | + |
| 73 | +``` |
| 74 | + |
| 75 | +**复杂度分析** |
| 76 | + |
| 77 | +- 时间复杂度:$O(2^moves)$,其中 moves 为题目答案。最坏情况 moves 和 N 同阶,也就是 $2^N$。 |
| 78 | +- 空间复杂度:$O(1)$。 |
| 79 | + |
| 80 | +由于题目数组长度最大可以达到 10^5, 这提示我们此方法必然超时。 |
| 81 | + |
| 82 | +我们必须考虑时间复杂度更加优秀的方式。 |
| 83 | + |
| 84 | +## 动态规划(记忆化递归) |
| 85 | + |
| 86 | +### 思路 |
| 87 | + |
| 88 | +由上面的解法, 我们不难想到使用动态规划来解决。 |
| 89 | + |
| 90 | +枚举所有的 l,r,x 组合,并找到最小的,其中 l 表示 左指针, r 表示右指针,x 表示剩余的数字。这里为了书写简单我使用了记忆化递归。 |
| 91 | + |
| 92 | +### 代码 |
| 93 | + |
| 94 | +代码支持:Python3 |
| 95 | + |
| 96 | +Python3 Code: |
| 97 | + |
| 98 | +> Python 的 @lru_cache 是缓存计算结果的数据结构, None 表示不限制容量。 |
| 99 | +
|
| 100 | +```py |
| 101 | +class Solution: |
| 102 | + def minOperations(self, nums: List[int], x: int) -> int: |
| 103 | + n = len(nums) |
| 104 | + |
| 105 | + @lru_cache(None) |
| 106 | + def dp(l, r, x): |
| 107 | + if x == 0: |
| 108 | + return 0 |
| 109 | + if x < 0 or r < 0 or l > len(nums) - 1: |
| 110 | + return n + 1 |
| 111 | + return 1 + min(dp(l + 1, r, x - nums[l]), dp(l, r - 1, x - nums[r])) |
| 112 | + |
| 113 | + ans = dp(0, len(nums) - 1, x) |
| 114 | + return -1 if ans > n else ans |
| 115 | +``` |
| 116 | + |
| 117 | +**复杂度分析** |
| 118 | + |
| 119 | +- 时间复杂度:$O(N^2 * h)$,其中 N 为数组长度, h 为 x 的减少速度,最坏的情况可以达到三次方的复杂度。 |
| 120 | +- 空间复杂度:$O(N)$,其中 N 为数组长度,这里的空间指的是递归栈的开销。 |
| 121 | + |
| 122 | +这种复杂度仍然无法通过 10^5 规模,需要继续优化算法。 |
| 123 | + |
| 124 | +## 滑动窗口 |
| 125 | + |
| 126 | +### 思路 |
| 127 | + |
| 128 | +实际上,我们也可以逆向思考。即:我们剩下的数组一定是原数组的中间部分。 |
| 129 | + |
| 130 | +那是不是就是说,我们只要知道数据中子序和等于 sum(nums) - x 的长度。用 nums 的长度减去它就好了? |
| 131 | + |
| 132 | +由于我们的目标是`最小操作数`,因此我们只要求**和为定值的最长子序列**,这是一个典型的[滑动窗口问题](../thinkings/slide-window.md)。 |
| 133 | + |
| 134 | +### 代码 |
| 135 | + |
| 136 | +代码支持:Python3 |
| 137 | + |
| 138 | +Python3 Code: |
| 139 | + |
| 140 | +```py |
| 141 | +class Solution: |
| 142 | + def minOperations(self, nums: List[int], x: int) -> int: |
| 143 | + # 逆向求解,滑动窗口 |
| 144 | + i = 0 |
| 145 | + target = sum(nums) - x |
| 146 | + win = 0 |
| 147 | + ans = len(nums) |
| 148 | + if target == 0: return ans |
| 149 | + for j in range(len(nums)): |
| 150 | + win += nums[j] |
| 151 | + while i < j and win > target: |
| 152 | + win -= nums[i] |
| 153 | + i += 1 |
| 154 | + if win == target: |
| 155 | + ans = min(ans, len(nums) - (j - i + 1)) |
| 156 | + return -1 if ans == len(nums) else ans |
| 157 | + |
| 158 | +``` |
| 159 | + |
| 160 | +**复杂度分析** |
| 161 | + |
| 162 | +- 时间复杂度:$O(N)$,其中 N 为数组长度。 |
| 163 | +- 空间复杂度:$O(1)$。 |
0 commit comments