资讯动态

数组去重与数组越界:一道NOIP初赛题背后的编程陷阱

发布时间:2026/9/17 15:50:39 来源:尧图企业网站定制
1. 开篇一道经典数组去重题四个选项居然只错了一个NOIP2008年普及组初赛的题目放在今天看依然是算法思维训练的好素材。我最近重刷这道题时发现第16题考察的是数组元素去重但它的难点不在“会不会去重”而在于你能不能一眼看出四个逻辑几乎相同的程序里哪一个的判断条件写错了。先说结论这道题的正确选项是C。但我更想聊的不是答案本身而是它在考什么、四个选项之间那一点点微妙的差异以及如果是我当年在初赛考场上遇到它应该怎么快速定位“错误源”。题目大致场景是这样程序要从键盘读入n个整数去掉重复的数值后把剩下的数按照原来的先后顺序输出。这四个选项都是同一个思路用一个标记数组或者计数数组来记录某个数是否出现过接下来遍历所有数遇到“没见过的数”就输出并且把它标记成“已经见过”。但这里面有一个非常容易踩坑的细节——重复判断是在输入时做还是先把所有数存下来再做。四个选项的差异就在这。2. 为什么数组越界是这种题最隐蔽的坑这道题的数据范围我记得很清楚输入的每个数不会超过100。很多同学看到“不超过100”这个条件第一反应是“那数组开个100不就够了”如果你这么想那这道题你大概率会错选。来看四个选项分别怎么处理这条边界A和B的设计思路是完全一样的区别只在判断条件上。它们先读入x然后用a[x]这个数组去标记“x这个数字是否出现过”。如果a[x]等于0说明x还没出现过那就先把它存进另一个数组b然后把a[x]置为1。这个逻辑没有任何问题。但问题出在数组a的容量上。如果你只把a开成101的大小下标0到100那当输入的数字恰好等于100时a[100]这个位置是可以正常访问的。可关键是很多同学的代码习惯是开a[100]这在C里下标只能访问到a[99]一旦读入的数字是100程序就会越界访问。这道题的C选项就是这么被设计出来的。C选项把a数组开成了100的大小但它又允许输入数字的范围是1到100。当程序执行到x100时访问a[100]就越界了。在初赛这种笔试场景下程序“看起来”还能跑但你无法保证它输出的结果是正确的。要知道数组越界在C里是未定义行为它可能恰好没有崩溃也可能覆盖了其他变量导致结果完全错误。3. 四个选项逐个拆解哪个是安全的哪个是“隐性炸弹”我先说A和B为什么是对的程序。第一它们把标记数组开得足够大。a数组的尺寸直接开到101以上保证下标100的访问是合法的不会越界。第二它们在读入时就完成了去重和记录不需要额外排序所以输出顺序天然保持输入的先后顺序符合题目要求。第三它们用0和1标记“是否出现过”的思路清晰、无歧义在淘汰赛制的判断题里这就是标准的、正确的方案。再说C选项错在哪。前面已经提到了它的a数组大小是100无法容纳下标100的合法访问。当读入的数据中含有x100时程序就会越界。虽然在实际运行中可能不会立即报错但从原理上讲这个程序不能保证在所有合法输入下都能产生正确输出。对于初赛这种只看程序执行结果的题目你没法说它“碰巧能跑通”就算正确。最后看D选项。D在结构上和A、B略有区别但它也是正确的它把去重逻辑写成自定义函数check每次输出前调用一遍。函数的功能是遍历已记录的数组看当前这个数是否已经出现过。只能说它多了一层函数调用但逻辑本身没有问题而且它对数组长度的要求也更宽松不存在C那种越界风险。我把这四个选项的关键差异整理成一张表方便你对照选项标记数组容量核心判断方式是否安全A足够大直接按值访问标记数组是B足够大直接按值访问标记数组是C100越界直接按值访问标记数组但容量不足否D视数据而定自定义函数线性查找去重是C选项说白了就是“思路对、容器不够”的典型代表。它和A、B唯一的区别就在于那个数组大小的定义。4. 这道题真正想教给你的从“写出正确”到“确保安全”说实话在真实比赛里没有人会给你提供一个“恰好越界也不会崩”的运行环境。初赛喜欢出这种题就是因为它在考察你有没有形成安全编程的肌肉记忆。我当年学数组的时候老师反复强调一句话数组下标永远要往大了开不要刚刚好。你要存100以内的数就开105、110甚至更大一点你要处理长度不确定的字符串就预留两倍空间。这不是浪费这是编程的基本修养。再说说这道题背后隐含的知识点。它考的是三个东西第一标记数组去重的思想。你用一个数组记录某个元素是否出现过遍历一次原数据把“第一次遇见”的元素挑出来这就是最基础的去重方案时间复杂度O(n)空间换时间。第二数组越界带来的隐患。C选项不是逻辑写错是资源分配写错。这种错误在笔试里最容易被忽略因为它不报错、不提示它就是静默地产生未定义行为。第三对输入数据范围的理解。题目说“每个数不超过100”那你的程序必须保证对x100这种情况依然正确而不是假设“一般不会考那么极端的边界值”。5. 如果考试时遇到这种题我的做题顺序这里分享一个我自己的做题习惯。碰到这种给出四段相似代码让你挑错的题我不会逐行读代码而是按下面这个顺序排查第一步看数组声明。先把所有数组的大小列出来对照数据范围计算一遍看有没有越界风险。这一步能解决一半以上的“找茬题”。第二步看循环边界。尤其是for循环的起止条件是in还是in是i从0开始还是从1开始这个配合数组下标看经常能发现隐藏问题。第三步看判断条件。像这道题里“if(a[x]0)”和“if(a[x]1)”虽然只差一个数字但逻辑完全相反。我一般会把判断条件翻译成自然语言比如“如果这个数还没出现过就输出它”再跟题目要求做比对。第四步看特殊情况。输入最极端值会怎样输入重复数据会怎样数据量最大时会不会超时把边界值代进去走一遍比读十遍代码都有用。这套流程用在十年前是有效的放在现在面对更复杂的初赛题也一样适用。它本质上不是“做题技巧”而是调试程序的基本功。6. 结尾留个问题给你C选项那种“数据范围恰好卡在数组边界”的陷阱其实在现实中经常发生。比如你写一个统计函数输入数据的取值范围是用户告诉你的但用户说谎了或者数据源变了你的数组就不够用了。这提醒我们写代码的时候永远别相信“数据的最大值就是这么多”这种话预留余量是成本最低的风险控制手段。如果你今天第一次接触数组去重我建议你把A和D两个程序都亲手敲一遍跑几组数据感受一下它们的差异。A的效率更高D的代码可读性更好没有绝对的对错只有不同场景下的取舍。至于C你可以在本地把数组故意改小然后输入一个100试试看会发生什么——注意有的编译器会直接崩溃有的会静默出错这正是未定义行为的可怕之处。搞懂这一道题比你刷十道重复题更有价值。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价