将 x 减到 0 的最小操作数 图解题解
这道题到底在问什么
- 输入
- nums=[1,1,4,2,3], x=5
- 输出
- 2
先想最直接的笨办法
扫完全程,和等于 target=8 的最长中间段是绿色这段 [0, 4]、长 5。留下它最长,拿走的就最少:答案 = 8 − 5 = 3 次。没有去枚举两端怎么拿,一遍滑窗就解决。(动画第 20 步)
最优解:一步一步想明白
- 3记住「拿两端和为 x = 留中间和为 target=8;拿最少 = 留最长」,下面每一格都在套它。
- 4这一排是数组,全是正整数。我们要找和正好等于 target=8 的最长连续段。窗口从空开始,右指针一格格扩,盯住窗口和 cur 和当前最长。
- 5右指针到第 0 位(值 1),加进窗口,窗口和变成 1。还没到 target=8,接着右扩。
- 6右指针到第 1 位(值 2),加进窗口,窗口和变成 3。还没到 target=8,接着右扩。
- 7右指针到第 2 位(值 3),加进窗口,窗口和变成 6。还没到 target=8,接着右扩。
- 8右指针到第 3 位(值 1),加进窗口,窗口和变成 7。还没到 target=8,接着右扩。
- 9右指针到第 4 位(值 1),加进窗口,窗口和变成 8。正好等于 target=8!这是一个候选中间段。
- 10这段绿色窗口 [0, 4] 的和正好是 8、长度 5。比之前的最长还长,最长中间段刷新成 5,对应操作数 8−5=3。
- 11右指针到第 5 位(值 1),加进窗口,窗口和变成 9。它比 target=8 大了(红),下一步要左缩。
- 12窗口和超了,左指针右移一格、把第 0 位的 1 移出,和降到 8。正好回到 target!
- 13这段绿色窗口 [1, 5] 的和正好是 8、长度 5。没超过已记的最长 5,操作数保持 3。
- 14右指针到第 6 位(值 2),加进窗口,窗口和变成 10。它比 target=8 大了(红),下一步要左缩。
- 15窗口和超了,左指针右移一格、把第 1 位的 2 移出,和降到 8。正好回到 target!
- 16这段绿色窗口 [2, 6] 的和正好是 8、长度 5。没超过已记的最长 5,操作数保持 3。
- 17右指针到第 7 位(值 3),加进窗口,窗口和变成 11。它比 target=8 大了(红),下一步要左缩。
- 18窗口和超了,左指针右移一格、把第 2 位的 3 移出,和降到 8。正好回到 target!
- 19这段绿色窗口 [3, 7] 的和正好是 8、长度 5。没超过已记的最长 5,操作数保持 3。
- 20扫完全程,和等于 target=8 的最长中间段是绿色这段 [0, 4]、长 5。留下它最长,拿走的就最少:答案 = 8 − 5 = 3 次。没有去枚举两端怎么拿,一遍滑窗就解决。
⚠️ 容易写错的地方
✗ 错:硬搜左边拿几个、右边拿几个
✓ 对:转成「留中间和为 target 的最长段」
两端组合是指数级,转化后一遍滑窗 O(n)
✗ 错:求出最长段就直接返回它
✓ 对:答案是 n − 最长段长度
最长段是「留下的」,要返回的是「拿走的次数」
✗ 错:忘了 target < 0 或 = 0 的特判
✓ 对:target < 0 返回 -1、target = 0 返回 n
x 比总和大就拿不出;x 等于总和就得全拿走
完整代码(Python / C++ / Java)
Python
from typing import List
class Solution:
def minOperations(self, nums: List[int], x: int) -> int:
target = sum(nums) - x
if target < 0:
return -1
if target == 0:
return len(nums)
left = cur = best = 0
best = -1
for right, v in enumerate(nums):
cur += v
while cur > target:
cur -= nums[left]
left += 1
if cur == target:
best = max(best, right - left + 1)
return -1 if best == -1 else len(nums) - bestC++
#include <algorithm>
#include <numeric>
#include <vector>
using namespace std;
class Solution {
public:
int minOperations(vector<int>& nums, int x) {
int target = accumulate(nums.begin(), nums.end(), 0) - x;
if (target < 0) return -1;
if (target == 0) return nums.size();
int left = 0, cur = 0, best = -1;
for (int right = 0; right < (int)nums.size(); ++right) {
cur += nums[right];
while (cur > target) cur -= nums[left++];
if (cur == target) best = max(best, right - left + 1);
}
return best == -1 ? -1 : (int)nums.size() - best;
}
};Java
import java.util.*;
class Solution {
public int minOperations(int[] nums, int x) {
int total = 0;
for (int v : nums) total += v;
int target = total - x;
if (target < 0) return -1;
if (target == 0) return nums.length;
int left = 0, cur = 0, best = -1;
for (int right = 0; right < nums.length; right++) {
cur += nums[right];
while (cur > target) cur -= nums[left++];
if (cur == target) best = Math.max(best, right - left + 1);
}
return best == -1 ? -1 : nums.length - best;
}
}复杂度
时间
O(n)
左右指针各只前进一遍,每个元素进出窗口各一次
空间
O(1)
只用 left/cur/best 几个变量
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 将 x 减到 0 的最小操作数 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么不直接对「两端怎么拿」做搜索?+
从两端拿,每一步有「拿左」或「拿右」两种选择,直接搜是指数级。转化成「中间留最长」后,中间留下的一定是一段连续子数组,用滑动窗口一遍 O(n) 就能找到最长,效率天差地别。
为什么答案是 n − 最长段,而不是最长段本身?+
最长段是「留下来不拿的」那部分。题目要的是「拿走的次数」,等于总长度 n 减去留下的长度。把这层「留 vs 拿」的互补关系说清,就不会答反。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 将 x 减到 0 的最小操作数 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。