news 2026/10/13 7:24:18

一次遍历搞定:数组求和与奇偶分类的编程题拆解

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
一次遍历搞定:数组求和与奇偶分类的编程题拆解

最近在给一位刚学编程的学员做作业辅导时,翻到一道非常典型的入门题:给定一个整数数组,求所有元素之和,同时把元素按奇偶分类统计。题目本身看着很简单,可实际写起来,我发现大多数人不是不会写循环,而是没有从“遍历—判断—累加”这条主线去理解问题。有人用内置求和方法算完总和,再单独写一遍循环做分类,白白遍历两次;有人在判断负数奇偶时写出了错误结果;还有人完全不设计测试用例,空数组直接让程序崩溃。

别小看这道题。它背后串着遍历框架、条件分支、累加变量、边界测试四条线,面试里也常拿它当热身题。这篇就把“数组元素求和与奇偶分类”这道例题彻底拆开讲透,覆盖求和写法、奇偶判定、完整实现、测试用例设计和几个常见变体。只要你把这一道题吃透,后面学排序、二分查找、前缀和都会顺很多。

1. 这道例题的隐藏考点:一次遍历搞定两件事

1.1 为什么求和和分类总被放在同一道题里

很多初学者觉得“求和”和“奇偶分类”是两个独立的知识点,放在一起只是老师随手拼的。其实它们共享同一个核心机制:遍历数组。

求和的本质是:依次访问每个元素,把它累加到一个变量里。奇偶分类的本质是:依次访问每个元素,用一个条件判断决定“这个值该进哪个统计变量”。所以这个例题真正考察的是,你能不能意识到“一次遍历可以做多件事”。

我见过一些学员的代码,写成这样:

  • 第一遍循环求总和。
  • 第二遍循环判断偶数,累加偶数和。
  • 第三遍循环判断奇数,累加奇数和。

功能是对的,但遍历了三遍。虽然时间复杂度仍然是 O(n),常数项变大,代码却明显冗余。更关键的是,这种写法说明写代码的人没有把握住“遍历过程中每一次都能同时做多种操作”这个思想。

正确的思考方式是:遍历的每一次迭代,都是一个“加工节点”。在这个节点上,你可以累加总和,可以判断奇偶,可以更新计数,也可以把符合条件的值放进另一个数组。这些操作彼此独立,完全可以放在同一个循环体里完成。

1.2 动手之前先画一遍“纸上演算”

我辅导学员时,总是要求他们先把程序“人肉执行”一遍,再写代码。就拿一个 5 元素的小数组举例。

假设数组是[5, -2, 0, 3, 8]。在纸上画一张表,每一行记录遍历到第几个元素时的状态:

当前元素总和偶数和奇数和偶数个数奇数个数
初始00000
550501
-23-2511
03-2521
36-2822
8146832

这张表走完,你对变量的变化过程就有了直观感觉。调试的时候,只要把程序输出和这张表比对,哪里出了问题一眼就能看出来。

这个习惯看似笨拙,其实是程序员的基本功。真正遇到复杂 bug 的时候,你总不能全靠 log 输出,而是要在脑子里模拟数据流动。一开始就把“纸上演算”练熟,后面写任何循环代码都稳得多。

2. 求和不是背代码:for、while、内建函数三种写法怎么选

2.1 for 循环:最稳的通用写法

先说最通用的 for 写法。以 Python 为例:

total = 0 for x in arr: total += x

这段代码大家都会写,但我想强调两点。

第一,这里用的是for x in arr,直接遍历元素值,而不是用下标for i in range(len(arr))。如果只求和,直接遍历元素更简洁、更不容易出错。如果用下标,还得每次写arr[i],代码可读性差一些。

第二,累加变量total一定要在循环开始前初始化。新手经常忘记,导致第一次累加时使用一个不存在的变量。

如果题目要求未来可能要改“每隔两个元素求和”,那么用下标遍历会更合适。但就本题而言,直接遍历元素是最自然的选择。

2.2 while 循环:什么时候它更合适

while 写法是这样的:

i = 0 total = 0 while i < len(arr): total += arr[i] i += 1

这种写法比 for 啰嗦,但它最大的优势是手动控制下标。当你需要在循环体内跳过某些元素、修改循环步长、甚至根据条件回退时,while 更灵活。

不过要注意一个最常见的坑:忘记写i += 1。忘写的后果就是死循环。初学阶段我建议少用 while,先用 for 把逻辑练熟,等确实遇到“需要复杂下标控制”的场景再切到 while。

2.3 内建求和函数:省事,但要分清场景

Python 里有现成的sum(arr),一行代码就得到总和。问题在于,本题还要做奇偶分类。如果你先用sum(arr)求总和,再另写一个循环做分类,虽然代码也能跑,但白白遍历了两遍。

更好的做法是:在同一个循环里既累加总和,也累加偶数和、奇数和。这样一次遍历全部搞定。

假如题目改成“只求和,不做任何分类”,那我会毫不犹豫用sum(arr)。工具要选对场景,没有绝对的最优写法,只有适合当前问题的写法。

2.4 三种写法的取舍表

写法代码量可读性灵活性适用场景
for 遍历中等好中等大多数数组遍历
while 遍历较多一般高需要手动控制下标、步长
内建 sum()最少极好低只求和、不做分支处理

三种写法的时间复杂度都是 O(n),额外空间基本都是 O(1)。所以性能上没有本质差别,差别全在代码表达力和维护性上。

如果你刚开始学,建议先把 for 循环版练熟。它能应对绝大多数场景,也是后续理解排序和搜索算法的基础。

3. 奇偶判定里最容易翻车的三个细节:负数、零和位运算技巧

3.1 负数取模:教科书里没讲透的坑

判断奇偶,很多教材会这么教:x % 2 == 0是偶数,x % 2 == 1是奇数。

前半句没问题,后半句在负数场景下就有隐患。以 C 语言为例,负数的余数符号与语言实现有关。-3 % 2的结果在很多 C 编译器里是-1,而不是1。这时候用x % 2 == 1判断奇数,就会把-3错误地排除掉。

Python 的情况又不同,-3 % 2的结果是1,因为 Python 的取模结果与除数符号一致。也就是说,同样一行判断,换一种语言结果可能完全不一样。

最稳妥的写法是:

  • 判断偶数:x % 2 == 0
  • 判断奇数:x % 2 != 0

用“不等于 0”而不是“等于 1”,无论正负都正确。这是一个值得记住的通用经验。判断一个属性的“否定面”,用“不等于”往往比“等于”更安全。

3.2 0 的归属和边界用例

0 % 2 == 0,所以 0 属于偶数。这一点多数人都知道,但写测试用例时却常常忽略。

我在辅导中见过一位学员,用数组[1, 3, 5]测程序,结果正确;换成[0]就报错,因为他没有对空数组做保护。还有人在分类时用了两个独立的 if,而不是 if-else,导致 0 被同时算进偶数和其他分支,统计数据全乱了。

0 是测试用例里性价比最高的元素之一,成本低,却能同时验证“边界值处理”和“分支结构”是否正确。以后你测试任何条件判断,都先往用例里塞一个 0。

3.3 位运算:用二进制最低位判断奇偶

除了取模,还有一种更底层的判断方式:看二进制的最低位。

一个整数是偶数,意味着它的最低位是 0;是奇数,最低位是 1。所以:

if x & 1 == 0: # 偶数 else: # 奇数

x & 1是按位与运算,只保留二进制最低位的值。这种写法在底层性能要求高的场景里更快,但对入门学习者来说,取模写法已经足够。

我之所以提它,是因为很多企业面试或竞赛题里,会有人用位运算判断奇偶。你至少要知道别人这么写是什么意思,不至于看到& 1就发懵。实际开发里,代码可读性通常优先于微小的性能差异,所以取模写成x % 2 != 0完全够用。

4. 一道完整例题的演练:输入、输出、用例与复杂度

4.1 题目完整描述

把前面的讨论汇总成一道完整的例题:

给定一个整数数组nums,请计算并输出:

  1. 所有元素之和;
  2. 所有偶数元素之和;
  3. 所有奇数元素之和;
  4. 偶数元素的个数;
  5. 奇数元素的个数。

这道题的设计合理性在于:总和等于偶数和加奇数和,这个关系可以作为程序正确性的一个验证手段。

4.2 参考实现:一次循环完成所有统计

def analyze_array(nums): """ nums: List[int] 返回: (total, even_sum, odd_sum, even_count, odd_count) """ total = 0 even_sum = 0 odd_sum = 0 even_count = 0 odd_count = 0 for x in nums: total += x if x % 2 == 0: even_sum += x even_count += 1 else: odd_sum += x odd_count += 1 return total, even_sum, odd_sum, even_count, odd_count

注意这里用的是if-else,而不是两个独立的if。因为一个整数非偶即奇,用了if-else以后,每个元素只进一个分支,统计结果不会重复。

你可能会问:为什么不把total += x也放进分支里?因为total需要涵盖所有元素,放在分支外面更直观,也不会重复累加。

如果你用的是 C 语言或 Java,循环写成:

int total = 0; int evenSum = 0, oddSum = 0; int evenCount = 0, oddCount = 0; for (int x : nums) { total += x; if (x % 2 == 0) { evenSum += x; evenCount++; } else { oddSum += x; oddCount++; } }

结构完全一样,只是语法不同。

4.3 测试用例设计:空数组、全偶、全奇、负数混合

这是我觉得这道题最有价值的部分。大多数初学者写完代码就跑一遍示例数组,发现结果“差不多”就交作业了。但真正专业的做法是,用一组有代表性的用例把代码的边界情况都打一遍。

输入数组期望总和偶数之和奇数之和偶数个数奇数个数验证意图
[]00000空数组保护
[0]000100 属于偶数
[2, 4, 6]1212030全偶数组
[1, 3, 5]90903全奇数组
[-4, -3, -2, -1]-10-6-422负数奇偶判定
[5, -2, 0, 3, 8]146832混合场景

每跑完一组用例,都顺便验证一下total == even_sum + odd_sum。如果相等,说明累加逻辑没有遗漏;如果不相等,说明某个分支出了问题。这就是我在后面会讲的“不变量校验”思想。

4.4 时间与空间复杂度

这个算法对每个元素做一次访问和常数次运算,所以时间复杂度是 O(n),其中 n 是数组长度。

额外空间方面,除了若干统计变量,没有创建新的数组或大规模数据结构,所以空间复杂度是 O(1)。这是“一次遍历”方案最突出的优点:时间上不浪费,空间上也不浪费。

对比一下“先 sum 再循环分类”的做法:如果只用sum()求总和,再单独分类,时间上是两次遍历,仍然是 O(n),但常数更大;如果分类时要分别创建偶数组和奇数组,空间就会变成 O(n)。初学者往往意识不到这些差别,等处理几十万条数据时,差别就会变得很明显。

5. 做完别急着交:五个变体把知识点吃透

5.1 变体一:只统计奇偶个数,不求和

如果题目改成“只输出奇数和偶数的个数”,我们只需要保留 count 变量,去掉和相关的累加。这个变体几乎没有难度,但它提醒你:同一套遍历框架,统计目标和累加目标可以自由组合。

很多入门练习题就是从这类小改动开始的。你别觉得简单就跳过,快速敲一遍,能加深对变量作用的印象。

5.2 变体二:把偶数移到数组前部

这是奇偶分类题目中非常经典的扩展:给定数组,要求把所有偶数放在前半部分,奇数放在后半部分,不要求保持原有顺序。

常见做法是双指针:

def move_evens_front(nums): left = 0 right = len(nums) - 1 while left < right: while left < right and nums[left] % 2 == 0: left += 1 while left < right and nums[right] % 2 != 0: right -= 1 if left < right: nums[left], nums[right] = nums[right], nums[left] left += 1 right -= 1 return nums

这个变体用到了“从两边向中间扫描”的思路,和快速排序的 partition 操作很像。它不再关心总和,而是专注于“分类后进行位置交换”。如果你能把这道变体写出来,说明你已经从“会遍历”进步到“会利用下标控制元素位置”。

5.3 变体三:把偶数和奇数分别存入两个新数组

有时候题目要求输出两个数组,比如所有偶数和所有奇数。实现时要用两个空列表,遍历时用append分别追加。

evens = [] odds = [] for x in nums: if x % 2 == 0: evens.append(x) else: odds.append(x)

很简单,但代价是额外空间变成 O(n)。这类写法很适合用来讲解“空间换时间”的概念。虽然这里时间没有变少,但理解额外空间的来源很重要。

5.4 变体四:统计偶数下标位置的元素和

把“判断元素奇偶”换成“判断下标奇偶”,就出现了一个新题目:

计算所有偶数下标位置的元素之和。

写法是把if x % 2 == 0改成if i % 2 == 0,同时要改用下标遍历:

total_even_index = 0 for i in range(len(nums)): if i % 2 == 0: total_even_index += nums[i]

这个变体告诉你,“奇偶分类”的对象不一定是数组元素,也可以是下标。很多算法题里,下标本身就是一种信息,例如“求交替和”“求波峰波谷”,都离不开下标参与判断。

5.5 变体五:利用“和关系”做自动校验

前面提过total == even_sum + odd_sum。把它写进程序,成为自动化测试的一部分,是一种简单有效的“不变量校验”思想。

很多初学者觉得测试就是“多 print 几次”,但真正的工程实践里,我们会把校验逻辑做成断言。例如:

assert total == even_sum + odd_sum

跑完主体逻辑后,如果断言失败,程序会立刻报错。这种方式比肉眼看输出靠谱得多,因为人的眼睛很容易忽略细微错误。把这个习惯保留到以后写复杂算法里,能帮你尽早发现系统性错误。

我个人辅导学员时,发现一个普遍现象:大家能写出主循环,但很少主动设计边界测试和量关系校验。这其实是编程思维从“能跑”向“可靠”转变的关键一步。把这道例题的五种变体都敲一遍,你对数组遍历和条件分支的理解会上一个台阶。

我做编程辅导这几年,发现最影响学习进度的不是语法,而是“不验证自己在做什么”。很多学习者习惯写完代码直接看结果,结果对就算完成,结果不对就开始乱改。真正稳妥的做法,是先在纸上走一个最小数组,再用边界用例去验证逻辑,最后用“总量等于分量之和”这类不变量做校验。整个过程多一点耐心,后面调任何 bug 都快很多。

如果你刚学完这道题,不妨把它当作起点。先按第 4 节的完整实现写通,然后用第 3 节的边界用例测一遍,最后挑两三个变体练习。等你能不查资料把“求和 + 奇偶分类 + 偶数前置”这三个版本都写出来,你的数组基本功就算是真正打牢了。

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

用AI教孩子管压岁钱:一套完整的少儿财商启蒙实操方法

春节过后&#xff0c;很多家长都经历过这样的对话&#xff1a;孩子拿着压岁钱兴冲冲跑过来问“妈妈能帮我收着吗”&#xff0c;大人一句“我先帮你存着&#xff0c;等你长大再给你”&#xff0c;然后钱就再无下文了。我今年换了个玩法&#xff0c;用AI教孩子管压岁钱&#xff0…

作者头像 李华
网站建设 2026/10/13 7:22:31

Grok Bot 从零到一:环境搭建、创建与拉群实战教程

1. 从零理解 Grok Bot&#xff1a;它到底能帮你干什么很多人第一次听到“Grok Bot”这个词&#xff0c;脑子里浮现的可能是某个聊天窗口里会回话的机器人。但真正用过一段时间之后你会发现&#xff0c;它的价值远不止“自动回复”这么简单。Grok Bot 本质上是一个可以接入群组、…

作者头像 李华
网站建设 2026/10/13 7:21:33

算法面试高频题型梳理与刷题复盘方法论

经常有人问我&#xff1a;算法题到底怎么刷才有效&#xff1f;网上题库动辄几千题&#xff0c;今天刷一道明天刷一道&#xff0c;最后面试该不会还是不会。我自己也经历过那个阶段&#xff0c;后来索性把所有碰过的题目重新做了一遍梳理&#xff0c;按照题型、难度、考点重新归…

作者头像 李华
网站建设 2026/10/13 7:20:30

Ubuntu开机自启脚本的正确姿势:systemd启动阶段精准控制

1. 项目概述&#xff1a;为什么 Ubuntu 开机自动执行脚本不是“配个 crontab -e 就完事”的事“ubuntu 开机自动执行脚本”——这行字在某高校实验室的 Linux 入门答疑群里&#xff0c;平均每周被问 7 次&#xff1b;在某外包公司运维交接文档里&#xff0c;是新同事踩坑率排名…

作者头像 李华
网站建设 2026/10/13 7:20:28

VMware 【13】设置非永久磁盘,验证关机后的数据变化

&#x1f525; 个人主页&#xff1a;杨利杰YJlio ❄️ 个人专栏&#xff1a;《Windows 疑难杂症与工单复盘案例库》 《Sysinternals实战教程》 《WINDOWS教程》 《Windows PowerShell 实战》 《IOS插件分析测试》 《超简单&#xff1a;用Python让Excel飞起来》 &am…

作者头像 李华