#LC1658. 将 x 减到 0 的最小操作数
将 x 减到 0 的最小操作数
你好,同学。今天我们要挑战的是一道非常经典的数组双指针/滑动窗口问题。这道题在 NOI 普及组(入门级)或提高组的初级阶段经常出现,它考察了选手“转换问题视角”的能力。
很多同学第一眼看到“从左端或右端移除元素”,会想到深搜(DFS)或动态规划(DP),但如果看到数据范围 ,我们就必须寻找更高效的线性 解法。
一、 预备知识点
- 正数数组的性质:所有元素均为正整数,这意味着前缀和是严格单调递增的。
- 补集转化思想:将“寻找两端之和为 的最小长度”转化为“寻找中间连续子数组之和为 的最大长度”。
- 滑动窗口(双指针):利用单调性,动态维护一段和为定值的区间。
二、 题目描述 (NOI 竞赛风格)
题目名称:将 x 减到 0 的最小操作数 (Minimum Operations to Reduce x to Zero)
【问题描述】
给定一个整数数组 nums 和一个整数 。每一次操作时,你应当移除数组 nums 最左边或最右边的元素,然后从 中减去该元素的值。请注意,你修改的是原数组,影响随后的操作。
你的目标是通过 最小次数 的操作,将 恰好 减到 。如果无法做到,输出 。
【输入格式】
第一行包含两个正整数 和 ,分别表示数组的长度和目标值。
第二行包含 个以空格分隔的正整数,表示数组 nums。
【输出格式】 输出一个整数,表示将 减到 的最小操作次数。如果不存在方案,输出 。
【样例 1 输入】
5 5
1 1 4 2 3
【样例 1 输出】
2
(解释:最佳方案是移除右侧的 3 和 2,共 2 次操作)
【样例 2 输入】
3 10
5 6 7
【样例 2 输出】
-1
【数据规模与约定】
三 : 启发式引导:草稿纸上的推理过程
请拿起笔,跟着我一步步分析:
第一步:逆向思维 (视角转换)
题目要求:左边切掉一段 + 右边切掉一段 = 。
思考: 剩下的中间那一段是什么?
剩下的中间部分是一个连续子数组。如果总和是 totalSum,那么中间那一块的和必须是:
第二步:目标重定义
- 原目标:两端移除的元素最少。
- 新目标:中间剩下的连续子数组长度最长。 这就变成了一个经典的“和为定值的最长子数组”问题。
第三步:画图模拟滑动窗口
假设 target = 5,数组为 [1, 1, 4, 2, 3]:
- 设置
left = 0,right = 0,当前窗口和currentSum = 0。 - 移动
right,把元素拉进窗口:[1],[1, 1],[1, 1, 4](此时和为 6,大于 5 了!)。
- 当
currentSum > target时,移动left缩小窗口:- 移除最左边的 1,变成
[1, 4],此时和恰好为 5。
- 移除最左边的 1,变成
- 记录当前长度
right - left + 1,并更新全局最大长度。
第四步:时间和空间复杂度分析
- 时间复杂度:每个元素最多被
right指针扫描一次,被left指针移出一次。总复杂度为 。 - 空间复杂度:只需存储原数组,空间复杂度为 。在 NOI 竞赛中, 的
int数组仅占约 0.4MB,远低于内存限制。
四、 NOI 风格 C++14 伪代码提示
#include <iostream>
#include <vector>
#include <numeric> // 用于求和
#include <algorithm>
使用命名空间 标准库;
// 全局变量定义
整型 数组[100005];
函数 解决() {
长长整型 总和 = 0;
读取 n, x;
对于 i 从 0 到 n-1 {
读取 数组[i];
总和 += 数组[i];
}
// 目标和:中间剩下的部分
长长整型 目标值 = 总和 - x;
// 边界情况判定
如果 (目标值 < 0) { 输出 -1; 返回; }
如果 (目标值 == 0) { 输出 n; 返回; }
整型 左指针 = 0, 当前窗口和 = 0, 最大长度 = -1;
对于 右指针 从 0 到 n-1 {
当前窗口和 += 数组[右指针];
// 窗口和太大,左边收缩
当 (左指针 <= 右指针 且 当前窗口和 > 目标值) {
当前窗口和 -= 数组[左指针];
左指针++;
}
// 找到符合条件的区间
如果 (当前窗口和 == 目标值) {
最大长度 = 取最大值(最大长度, 右指针 - 左指针 + 1);
}
}
// 最终答案是 总长度 - 最长中间段长度
如果 (最大长度 == -1) 输出 -1;
否则 输出 n - 最大长度;
}
五、 总结:读题关键词与优化建议
读题关键词:
- “最小次数” + “数组两端移除”:这类题通常可以转化为中间连续部分的最大长度。
- “正整数”:这是使用滑动窗口的关键信号!如果数组里有负数,区间和就不再具有单调性,必须使用前缀和+哈希表(Map)来解决。
优化建议:
- 数据溢出:注意 可能很大,虽然
nums[i]之和在 时可能达到 (仍在int范围),但为了保险,求和变量建议使用long long。 - 无法达成的情况:如果
totalSum < x,直接输出-1,避免无效计算。 - IO 优化:NOI 比赛中,处理 级别数据建议添加
ios::sync_with_stdio(false); cin.tie(0);。
教练点评: 这道题的难点不在于代码实现,而在于数学模型的转化。在草稿纸上画出“总长 = 两端 + 中间”的关系图,比直接写代码重要得多。这种“正难则反”的转化技巧,是金牌选手的基本功。加油!