昨天晚上看到一道题题目标题写的是 2026.3.3 最值问题要求用循环和数组来解。题目本身不长给定 N 个整数找出最大值、最小值并输出它们第一次出现的位置。很多刚学编程的朋友一看到“最值”两个字脑子里马上想排序其实方向就偏了。这类题真正要考的是两件事你能不能把数组按顺序走一遍能不能在走的途中记住最关键的信息。这篇题解我按自己实际做题的顺序来写先讲为什么再贴代码最后聊几个提交时最容易踩的坑希望能让你一次看明白。1. 题目分析与破题思路1.1 先看清题别急着敲代码这道题的原题描述大概是这样的输入一个正整数 N表示数组的长度第二行是 N 个整数。要求输出两行第一行是最大值和它第一次出现的位置第二行是最小值和它第一次出现的位置。位置从 1 开始计数。举个例子5 3 1 4 1 5正常输出应该是5 5 1 2这里最大值是 5在数组第 5 个位置最小值是 1数组里有不止一个 1第一次出现的位置是第 2 个。如果你写代码时不注意“第一次出现”这个条件很可能把最小值位置输出成 4。这道题表面上只是求最大最小值但它把循环、数组、下标映射、比较符号全都串起来了。很多人喜欢一上来就写代码结果写了半天还是差错其实就是因为没把题目里的“第一次出现”和“位置从 1 开始”这两句话当回事。1.2 最值问题的本质一次完整的遍历数组是什么你可以把它想象成一排带编号的箱子。循环是什么就是让你按顺序把这排箱子一个一个打开来看。求最值这个动作本质上就是在遍历完所有箱子之后记住那个“最重”或者“最轻”的到底在哪。所以排序在这儿是个误区。排序当然能轻松找到最大最小值但排序之后元素的位置就全变了题目还要输出原来的位置你得额外记录每个元素原来的下标多写一堆代码不说复杂度也上去了。真正合适的做法是线性扫描把数据装进数组用一个循环从头到尾访问一边访问一边记录当前见到过的最大值、最小值以及它们的位置这个思路叫“打擂台”也是整个题解的核心。1.3 动手前先想清楚三个问题我每次做这种题都会先在心里问自己三个问题。第一下标到底是从 0 开始还是从 1 开始。C 数组天然从 0 开始但题目要求位置从 1 开始。你可以选择数组从 1 开始存输出时直接输出下标也可以选择数组从 0 开始存输出时把下标加 1。两条路都行但你必须全程一致不能混着用。第二用什么变量记录最值和位置。常见做法是 maxVal、minVal、maxPos、minPos 四个变量。初始值最好直接用数组的第一个元素而不是随便初始化成 0 或者 INT_MAX。因为如果数据里全是负数把 maxVal 初始化为 0 就会出错。第三用一次循环还是两次循环。求最大值和最小值一次循环完全够用。一次循环里同时做两个判断代码反而更清晰。如果你分开求先找最大值再找最小值就要多写一遍循环还容易漏掉边界。2. 循环与数组的核心细节拆解2.1 数组到底开多大为什么多开几个格子很多新手写 C 代码时喜欢写int a[n]但 n 是变量这在标准 C 里不是合法的数组长度。有些编译器支持这种写法这是 GCC 的扩展换个环境可能就编译不过了。所以我一般用固定长度的数组const int MAXN 100005; int a[MAXN];为什么要开成 100005 而不是刚刚好 100000因为如果题目说 N 最大是 100000而你又想让下标从 1 开始那么你需要访问 a[100000]数组长度至少得是 100001。再多开几个格子比如 100005是为了防止你循环边界写错时数组越界。数组越界是 C/C 最狠的坑因为它不会像 Python 那样立刻报错给你看。越界后程序可能读到了脏数据也可能悄悄把其他变量的值改掉最后表现就是答案莫名其妙出错。多开 5 个格子不会让你彻底避免越界但至少能减少一部分边界问题。2.2 for 循环的边界和位置映射别写反如果你选择数组下标从 1 开始读取循环长这样for (int i 1; i n; i) { cin a[i]; }如果你选择数组下标从 0 开始读取循环长这样for (int i 0; i n; i) { cin a[i]; }这两种写法都常见但第一种输出位置更省事因为题目位置从 1 开始而你的循环变量 i 恰好就是从 1 到 n。第二种需要在输出时写i 1有时候一忙起来就忘了加导致位置整体偏移一位。我倾向于推荐下标从 1 开始的做法尤其在竞赛题里很多题目描述的位置都是从 1 开始代码和题面保持一致查错时更不容易晕。唯一要注意的是a[0] 这个位置你丢弃了不用数组大小要多开一个这正好呼应上面说的 100005。2.3 打擂台比较法为什么严格要求用大于和小于求最值的核心代码只有几行int maxVal a[1], minVal a[1]; int maxPos 1, minPos 1; for (int i 2; i n; i) { if (a[i] maxVal) { maxVal a[i]; maxPos i; } if (a[i] minVal) { minVal a[i]; minPos i; } }第一次看到这个代码的人可能会问为什么不是和因为题目要的是“第一次出现的位置”。如果你写if (a[i] maxVal)那么遇到和当前最大值相等的元素时擂台会被后续元素抢走位置就会不断更新成后面的。比如数组是3 3 2最大值是 3第一次出现位置应该是 1但用了之后最终位置会变成 2。最小值同理用了就会把最后一次出现的位置当作结果。所以这里必须用严格大于和严格小于只有在遇到“严格更优”的元素时才更新擂台。如果你哪天遇到题目要“最后一次出现的位置”再改成和这才是正确的灵活变通。3. 完整实操过程与代码实现3.1 C 参考代码逐行解释完整代码我放在这里你直接抄到本地跑一下然后一行一行对照着看#include iostream using namespace std; const int MAXN 100005; int a[MAXN]; int main() { ios::sync_with_stdio(false); cin.tie(0); int n; cin n; for (int i 1; i n; i) { cin a[i]; } int maxVal a[1]; int minVal a[1]; int maxPos 1; int minPos 1; for (int i 2; i n; i) { if (a[i] maxVal) { maxVal a[i]; maxPos i; } if (a[i] minVal) { minVal a[i]; minPos i; } } cout maxVal maxPos \n; cout minVal minPos \n; return 0; }先说第一行的#include iostream它是输入输出流头文件。using namespace std;让你不用每次写std::cin但对初学者来说知道它和 cin 有关就够了。int a[MAXN];我放在了 main 函数外面这样数组是在全局区分配。全局数组比函数内部的局部数组抗造得多尤其当 N 接近 100000 时局部数组占用的栈空间可能让程序崩溃。尽管 100000 个 int 也不算特别大但养成全局数组的习惯能少踩很多四位数的大坑。ios::sync_with_stdio(false);和cin.tie(0);是两行调优语句作用是让 cin 和 cout 的输出速度更快。数据量小的时候无所谓但题目数据到十万级别这两行能明显减少超时概率。如果你不喜欢完全可以直接删掉代码逻辑不受影响。进入主流程后第一步读 n第二步用循环把所有整数读进数组。注意循环从 i1 开始到 in 结束用的是。如果脑子一热写成i n最后一个数据就读不进去后面所有答案都会错。接下来初始化四个变量为 a[1] 的值和位置 1。这里有一点很重要如果数组只有一个元素内层循环从 i2 开始直接不会进入输出的还是正确的 a[1] 和位置 1。这就是用 a[1] 做初始值的好处不需要单独处理 N1 的情况。最后输出时最大值和最小值放在两行。我用的是\n不是endl。两者都能换行但endl会强制刷新输出缓冲区大量输出时更慢竞赛代码里我一般都用\n。3.2 Python 对照实现看懂下标映射如果你在用 Python 刷题参考代码会更短n int(input()) a list(map(int, input().split())) max_val a[0] min_val a[0] max_pos 1 min_pos 1 for i in range(1, n): if a[i] max_val: max_val a[i] max_pos i 1 if a[i] min_val: min_val a[i] min_pos i 1 print(max_val, max_pos) print(min_val, min_pos)Python 的 list 就是动态数组不需要像 C 那样提前声明长度直接用list(map(int, input().split()))把整行数字拆出来转成整数列表。注意 Python 的列表天然从 0 开始而题目位置从 1 开始所以我在更新 max_pos 时写的是i 1这就是前面说的下标映射。循环写的是for i in range(1, n)range 的结束位置是开区间也就是说会访问a[1]到a[n-1]也就是列表中除了第一个元素之外的全部元素。第一个元素已经在初始化时用掉了所以这样写是对的。如果你不小心写成range(1, n 1)程序会越界报错。这段代码和 C 版本在逻辑上完全一致只是语言换了一下。如果你是那种坚持只用一门语言刷题的初学者我建议至少把这两个版本都看一遍因为它们能帮你把“算法逻辑”和“语言语法”分开。很多同学写 C 会看 Python 看不懂不是算法不会而是没搞明白两边数组下标和 for 循环的对应关系。3.3 扩展题如果题目再加一个“求次大值”刷题网站特别喜欢在最值题后面再问一句你能不能找出第二大的数这套逻辑很有意思也很适合用来检验你是不是真的理解了循环里的更新顺序。求最大值时我们只维护一个变量 maxVal但求次大值需要两个变量max1 和 max2。max1 存当前最大max2 存当前次大。每来一个新数 x分两种情况如果 x 比 max1 大那原来的 max1 就降级成 max2x 成为新的 max1。如果 x 没超过 max1但比 max2 大那 x 更新 max2。写成伪代码大概是int max1 a[1]; int max2 -2147483648; for (int i 2; i n; i) { if (a[i] max1) { max2 max1; max1 a[i]; } else if (a[i] max2 a[i] ! max1) { max2 a[i]; } }这里有个小陷阱如果数组里允许重复最大值比如5 5 3那第二大的数到底是 5 还是 3不同题目定义不一样。有的题目要求“严格次大值”就是比最大值小的最大数那 5 的重复就不算答案应该是 3。有的题目把重复的数也算那第二大的就是 5。你写代码前必须看题别一上来就抄。这个扩展题告诉我们一个道理循环里变量的更新顺序非常讲究。先更新谁、后更新谁直接决定结果正确与否。你把这个例子想通了最值题就不只是会抄代码而是真正会设计算法。4. 常见问题与避坑实录4.1 高频报错速查表刷题的时候最烦的就是报错我把这类题常见的报错整理成了一个表你可以存下来当参考。现象常见原因解决办法Segmentation fault数组开小了或者访问了下标越界把数组改成至少 MAXN 5检查循环边界Wrong Answer下标从 0 开始但输出位置时没加 1统一用 0-based 存输出时写 i 1Wrong Answer用了 和 导致输出的是最后一次出现的位置改成严格大于和严格小于Wrong AnswermaxVal 初始化为 0但数据全是负数用 a[1] 做初始值Time Limit Exceeded使用了双层循环暴力比较改成一遍线性扫描复杂度 O(N)Compile Error用了 int a[n]n 是变量改成 const MAXN 固定数组我的实测经验是这道题报 Wrong Answer 的人里至少有三分之一是位置错三分之一是初始值错剩下三分之一才是循环边界错。位置错的比例比我预想的高很多所以你在写完代码之后第一件事就应该检查输出位置是不是整体偏移了一位。4.2 边界数据自测清单提交到 OJ 之前我建议先本地跑几个边界数据。这是我在实际刷题中觉得最有效的习惯。第一个测试用例N 等于 11 42正确输出应该是42 1 42 1因为只有一个元素它既是最大值也是最小值位置都是 1。如果这个用例跑不对说明你的 N1 处理有问题。第二个测试用例所有元素都相等5 7 7 7 7 7正确输出应该是7 1 7 1这里最能检验你是不是用了严格大于和严格小于。如果你用最小值位置很可能会变成 5。第三个测试用例数据里有负数3 -5 -2 -3正确输出应该是-2 2 -5 1如果 maxVal 初始化为 0这个用例输出会变成 0 开头的错误结果因为你根本没有见过 0 这个元素。第四个测试用例最大值和最小值相邻4 9 1 9 1正确输出应该是9 1 1 2这个用例可以检查两个判断条件是否独立。有的新手写代码时用 if-else 把最大值和最小值判断连起来结果最大值更新之后最小值判断被 else 跳过导致答案错了。正确写法是两个独立的 if不是 if-else。4.3 循环和数组调试的三个独家技巧第一个技巧是“人肉模拟”。拿一张草稿纸写一个很小的数组比如2 4 1 3然后按照你的代码一行一行走记录 maxVal、minVal、maxPos、minPos 的值最后和程序输出对比。这个办法看起来很笨但真的能最快找到位置错在哪里。第二个技巧是临时输出调试信息。在循环里加一句cout i i a[i] a[i] maxVal maxVal maxPos maxPos \n;这一步不用藏着掖着本地调试怎么方便怎么来。提交之前删掉就行。我见过太多同学靠肉眼看代码看不出问题其实加三行中间输出就能一眼定位。第三个技巧是从数据逆推逻辑。如果答案里最大值位置和最小值位置都偏后很可能是你用了和。如果只有一个位置错就检查你是从 0 开始还是从 1 开始。这个原则能帮你少走不少弯路。5. 从最值问题延伸出去的算法思维5.1 数组不只是存数还是算法思维的载体这道题很多人做完就丢一边了但我觉得它很适合作为“数组体检”。如果你能把这个题完全吃透你就掌握了数组最基础的两种操作存数和遍历。之后再学前缀和、滑动窗口、桶计数都会轻松很多。举个例子二维数组的最值问题其实就是一个外层循环套一个内层循环把每个格子都访问一遍。树状数组和线段树这些看起来高级的数据结构本质上也是在维护数组上的最值、区间和这类信息。它们的基础依然是循环和数组。很多高阶内容看不懂真不是智商问题而是最基础的遍历思想没练扎实。5.2 遇到“循环 数组”组合题先想三件事我后来刷了差不多两百道数组题总结出一个固定的解题顺序每次打开题目都会在心里过一遍第一数据怎么进数组下标从 0 还是 1。第二循环从哪开始在哪结束边界条件是什么。第三循环体里要更新哪些变量用什么符号更新。这个顺序看着简单但它能帮你在动手写代码之前先把整个程序的骨架立住。很多人一上来就写for结果写到一半发现数组空间不够或者输出位置映射反了又要推倒重来。先花三分钟把这三个问题想清楚比早写三分钟代码划算得多。5.3 一个真实经验最值题是刷题最好的“药引子”我个人的体会是最值问题是入门阶段性价比最高的一类题。它不会让你产生“这算法好难”的挫败感但只要稍微改一改条件比如“求最长连续上升段”“求最大子段和”它又能无缝衔接到更经典的算法题里。所以如果你今天刚被这道 2026.3.3 的最值问题难住别气馁。你把上面几段代码多敲几遍把边界用例跑熟然后试着给自己出几个变式题比如把数组倒序输入、把数组元素改成浮点数、把要求改成输出所有最值的个数。等你把这些变式都搞定了循环和数组这一关就真的迈过去了。