news 2026/9/13 18:00:31

LeetCode 1658 最小操作数减 X 到零:Go 实现滑动窗口逆向思维全解析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 1658 最小操作数减 X 到零:Go 实现滑动窗口逆向思维全解析

LeetCode 1658 最小操作数减 X 到零:Go 实现滑动窗口逆向思维全解析

【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go

导读

LeetCode 1658 是一道经典的"两端取数"问题:每次操作只能从数组最左端或最右端移除一个元素并从x中减去其值,求恰好将x减到 0 所需的最小操作数。本文以 LeetCode-Go 仓库中 1658.Minimum-Operations-to-Reduce-X-to-Zero 的官方题解为骨架,完整还原其核心解题思路——把"两端的数字最少"逆向转化为"中间连续子数组最长",并给出可直接运行的 Go 源码、测试用例与复杂度分析。读完本文,你将掌握这类"两端操作"问题通用的滑动窗口转化技巧。

题目理解

给定一个整数数组nums和一个整数x,每一次操作时,应移除数组nums最左边或最右边的元素,然后从x中减去该元素的值,并且需要修改数组以供接下来的操作使用。如果可以将x恰好减到 0,返回最小操作数;否则返回-1

原文档给出了三个示例:

示例 1: Input: nums = [1,1,4,2,3], x = 5 Output: 2 解释:最优解是移除最后两个元素,即可把 x 减到 0。 示例 2: Input: nums = [5,6,7,8,9], x = 4 Output: -1 示例 3: Input: nums = [3,2,20,1,1,3], x = 10 Output: 5 解释:最优解是移除最后三个元素和前两个元素(共 5 次操作),即可把 x 减到 0。

数据约束(原文档 Constraints):

  • 1 <= nums.length <= 10^5
  • 1 <= nums[i] <= 10^4
  • 1 <= x <= 10^9

题目大意是:从数组两端分别移除一些数,使得这些被移除的数加起来正好等于整数x,要求输出最小操作数,否则返回-1

核心思路:逆向转化为"最长连续子数组"

这是本题最关键的一步思维转换,也是原文档解题思路的核心:

  • 要求输出最小操作数,即数组两头的数字个数最少,并且加起来和正好等于x
  • 由于要操作的位置在数组的两头,直接用 2 个指针分别操作不太方便。原文档作者当时解题时的思路是把它变成循环数组,这样两边的指针就在一个区间内了,再利用滑动窗口找一个最小的窗口,使得窗口内累加和等于整数x。这个方法可行,但代码较多。
  • 更优美的做法:要想两头的长度最少,也就是中间这段的长度最大。这样就转换成直接在数组上使用滑动窗口求解:寻找累加和等于一个固定值的连续最长子数组

具体而言,令数组总和为total,那么中间连续子数组的目标和为:

target = total - x
  • target < 0:说明数组总和都小于x,无论怎么移除都无法减到 0,直接返回-1
  • target == 0:说明必须把整个数组全部移除,操作数就是len(nums)
  • 否则,用滑动窗口在nums上寻找和为target最长连续子数组,其长度为res,答案即为len(nums) - res

这个逆向转化把"从两端取数"这种不便于双指针直接处理的场景,规约成了经典的"定和最长子数组"问题,时间复杂度由可能的指数级搜索降为一次线性扫描。

边界情况分析

在动手写代码前,先梳理清楚三类边界情况:

  1. total < x:数组内所有元素之和都小于x,两端移除不可能凑出x,答案是-1
  2. total == x:需要把整个数组全部移除,操作数为nn为数组长度)。
  3. 存在恰好等于target的子数组:找到其中最长的那个,答案取n - 最长长度;若找不到任何和为target的连续子数组,则答案同样为-1

这些边界分支在仓库源码 1658. Minimum Operations to Reduce X to Zero.go 的前半部分均有显式处理。

Go 源码实现逐行解析

仓库中的完整实现如下(与 README.md 中给出的代码一致):

package leetcode func minOperations(nums []int, x int) int { total := 0 for _, n := range nums { total += n } target := total - x if target < 0 { return -1 } if target == 0 { return len(nums) } left, right, sum, res := 0, 0, 0, -1 for right < len(nums) { if sum < target { sum += nums[right] right++ } for sum >= target { if sum == target { res = max(res, right-left) } sum -= nums[left] left++ } } if res == -1 { return -1 } return len(nums) - res } func max(a, b int) int { if a > b { return a } return b }

关键步骤说明

  • 第 3-7 行:先遍历一遍数组求出总和total,得到中间子数组的目标和target = total - x
  • 第 8-13 行:处理两类边界情况——target < 0直接返回-1target == 0返回len(nums)
  • 第 15 行:初始化滑动窗口。leftright为窗口左右边界(左闭右开),sum维护窗口内元素和,res记录和为target的最长窗口长度,初始为-1表示尚未找到。
  • 第 16-19 行:当sum < target时,右指针right向右扩张,把新元素纳入窗口。
  • 第 20-26 行:当sum >= target时进入内层循环:若sum == target则用max(res, right-left)更新最长长度;随后把左指针元素移出窗口(sum -= nums[left]; left++),继续收缩寻找更优解。
  • 第 28-32 行:若始终没有找到和为target的窗口(res == -1),返回-1;否则返回len(nums) - res,即需要移除的元素个数。

注意res = max(res, right-left)right-left恰好是当前窗口长度(因为right已指向窗口右开边界),这也解释了为何源码在sum == target时直接以right-left参与比较。

复杂度分析

  • 时间复杂度O(n)。数组只被完整遍历一次用于求和,滑动窗口的左右指针各至多移动n次,整体线性。
  • 空间复杂度O(1)。仅使用若干整型变量,没有额外数组或哈希表。

该解法可以在一次线性扫描内同时完成"找定和窗口"与"求最长长度"两个目标,相比"构造循环数组 + 双指针"的朴素做法,代码量更少、边界更清晰。

测试用例与验证

仓库配套的测试文件 1658. Minimum Operations to Reduce X to Zero_test.go 使用表格驱动(table-driven)风格组织用例,完整覆盖了以下场景:

numsx期望输出覆盖点
[1,1,4,2,3]52原题示例 1,只移除右侧两个元素
[5,6,7,8,9]4-1原题示例 2,无法凑出目标值
[3,2,20,1,1,3]105原题示例 3,左右两侧都要移除
[1,1]5-1total < x的边界分支
[1,2,3]63total == x,需移除全部元素

其中最后两个用例对应本文前面分析的边界分支:[1,1]总和为 2 小于x=5,直接命中target < 0返回-1[1,2,3]总和恰好等于x=6,命中target == 0返回len(nums) == 3。测试通过got != a.one判定失败并打印input/output,可以在仓库根目录执行go test ./leetcode/1658.Minimum-Operations-to-Reduce-X-to-Zero/ -v复现运行结果。

解法思路的延伸:同类题推荐

原文档在解题思路末尾明确给出了与本题思路相似的三道推荐题目:

  1. 209. Minimum Size Subarray Sum:同样是滑动窗口求解连续子数组和问题,不过本题求的是和≥ s最短连续子数组,与 1658 的"定和最长"恰好互为镜像。本仓库已有完整实现,参见 209.Minimum-Size-Subarray-Sum;
  2. 1040. Moving Stones Until Consecutive II:涉及循环数组与"端点移动"限制的思维题,其最小步数求解同样借助了定长滑动窗口,参见 1040.Moving-Stones-Until-Consecutive-II;
  3. 325. Maximum Size Subarray Sum Equals k:与 1658 的逆向转化几乎同构——求"和为固定值的最长连续子数组",区别在于 325 允许负数(需借助哈希表维护前缀和),而 1658 因数组全为正数可直接用滑动窗口,本题在 LeetCode 上需要付费订阅查看。

从源码结构可以推断,本仓库将 209 与 1040 的题解(含_test.go测试文件)以同样的目录规范组织,便于横向对比学习这三道题在滑动窗口运用上的异同。

总结

LeetCode 1658 的精髓在于逆向思维:与其直接模拟"从两端移除元素"的过程,不如把问题等价地改写为"找到和为total - x的最长连续子数组",再用一次线性滑动窗口求解。本仓库的 Go 实现仅用约 30 行代码即完成全部逻辑,并通过表格驱动测试覆盖了原题示例与边界分支。掌握这一转化模式后,无论是本题,还是 209、1040、325 等同类滑动窗口题目,都可以举一反三、快速定位解法。

【免费下载链接】LeetCode-Go✅ Solutions to LeetCode by Go, 100% test coverage, runtime beats 100% | LeetCode 题解项目地址: https://gitcode.com/GitHub_Trending/le/LeetCode-Go

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/13 18:00:09

OpenClaw插件生态:15款高效工具与开发实践

1. OpenClaw插件生态概述 OpenClaw作为一款新兴的多功能自动化工具&#xff0c;其强大之处在于开放的插件架构设计。2026年版本通过模块化设计实现了功能解耦&#xff0c;核心系统仅保留基础运行环境&#xff0c;90%以上的功能实现都交由插件完成。这种架构带来的直接优势是用户…

作者头像 李华
网站建设 2026/9/13 17:59:56

WorkBuddy创建专家全攻略:从智能体设计到自动化落地

1. WorkBuddy不像你想的那么简单——先搞懂“创建专家”到底在做什么我最早接触WorkBuddy&#xff0c;是看到有人拿它处理表格、盯群消息、定时发周报&#xff0c;以为又是一个套壳的聊天机器人。真正上手之后才发现&#xff0c;这个工具的底层逻辑完全不是“你问我答”&#x…

作者头像 李华
网站建设 2026/9/13 17:57:46

STM32驱动DS1302实时时钟芯片的微秒级时序与抗干扰实战

1. 项目概述&#xff1a;为什么一个实时时钟芯片值得花一整天去“较真”STM32 驱动 DS1302——这行标题看起来平平无奇&#xff0c;像极了嵌入式初学者在实验室里随手记下的一页草稿。但如果你真把它当成“照着例程抄一遍就能跑通”的小任务&#xff0c;大概率会在第三天凌晨两…

作者头像 李华
网站建设 2026/9/13 17:56:59

MySQL 联合查询

联合查询是工作中用的最多的查询,而且面试的时候也非常爱考,因为SQL没啥考的难点,联合查询在SQL中稍微复杂。一、联合查询的简单理解联合查询是联合多个表进行查询&#xff0c;设计数据是把表进行拆分&#xff0c;为了消除表中的字段的依赖关系&#xff0c;比如部分函数依赖&am…

作者头像 李华