周三晚上十一点,工程师陈卓拧开第三瓶无糖可乐,点开了欧拉计划的第一题。那个问题安静地躺在屏幕上,像所有经典的初学者陷阱一样,外表温柔、内核锋利。“把1000以下所有3或5的倍数加在一起。” 他下意识地敲出了一个 for 循环,一行行代码像肌肉记忆般流出。跑出结果233168只用了半秒,他正想关掉页面,突然被一句话拽住了:“如果上限是一个亿呢?”
不是性能不够,是脑袋里那根“先跑循环再说”的弦绷得太久了。陈卓盯着刚刚写完的代码,心里开始了一场辩论,正方和反方都来自他自己。正方说:问题这么小,直接遍历最简单,可读性满分,同事半夜接手也能看懂。反方反驳:可读性不是懒惰的借口,一千、一万你遍历,一亿、十亿也遍历吗?养成习惯再想改就难了。他看着那一行 if number % 3 == 0 or number % 5 == 0,突然意识到,这其实是在问一个数学问题,而自己一直在用物理方式回答。
![]()
暴力解法确实直白:从1数到999,每个数都用取余运算符 % 验一下,能被3或5整除就累加。Python 里 number % 3 返回余数,余数为零便说明没有余数——整除成立。代码就是一套机械流水线,送进数字,流出判断,统计求和。对于1000,这不过一千次检查,现代处理器眼皮都不眨。但陈卓慢慢看穿了它的真面目:这是一个 O(n) 的方案,工作量跟输入量成正比。上限翻倍,循环次数翻倍;上限膨胀到十亿,就要做十亿次整除判断。跑得动不代表跑得好——你让银行把每一笔历史交易都循环一遍来算利息,数据中心会直接拉警报。
反方立场开始亮出底牌:问题的本质不是“数字的逐一筛查”,而是“3和5的倍数构成的等差数列”。3的倍数就是3, 6, 9, 12…… 每一项都比前一项固定多出3,这正是算术级数的定义。高斯小学时就算出了1加到100的和,公式简洁得像一句诗:和 = 首项加末项的和除以2再乘以项数,也就是 n×(n+1)/2。对于3的倍数,只不过每一项都带着公因子3,只要把3提到外面,括号里还是连续自然数的和。所以,3的所有倍数求和,等价于 3 × [ (项数)×(项数+1)/2 ]。这里的项数等于 (上限−1) 整除3,也就是 floor((limit−1)/3)。同理可得5的倍数总和。
到这里,正方又开口了:“等等,这两个集合有重叠——15既是3的倍数又是5的倍数,你把两笔钱加起来的时候,15、30、45就被数了两次,等于重复报销。”这恰好引出了容斥原理:要把多算的部分减掉。于是最终答案变成三个等差数列之和的加减法:sum(3的倍数) + sum(5的倍数) − sum(15的倍数)。写成代码,一个通用的辅助函数 sum_of_multiples 接收参数 x 和 limit,先用整数除法“//”算出项数 n(它自动向下取整,恰好给出完整倍数的个数),然后直接套 x * n * (n+1) // 2。主函数 project_euler_1 就变成一行组合返回。陈卓把代码跑了一遍,还是那个结果233168,但他知道事情已经完全不一样了。
他用几分钟画了一张对比表,把两种思路的内核赤裸裸摆在面前。上限1000时,暴力法大约做1000次运算,公式法只需几十次简单乘法加法;上限100万,暴力法约百万次工作,公式法还是几十次;上限10亿,暴力逼近十亿次,公式仍是两位数级别的常量计算。从算法复杂度看,这就是 O(n) 与 O(1) 的距离。O(1) 意味着不管输入量多大,核心计算成本几乎不变——它不是在数数,是在计算。循环在那里一个个敲门,公式却直接砸开了保险柜。
这不仅仅是欧拉计划第一题的花边。陈卓立刻想到了数据库里 B+树索引为什么能瞬间定位数据,而不是一行行扫描全表;想到了现代密码学里大数取模运算其实依赖数学性质,而不是循环试除;想到了机器学习里向量化操作把循环压进底层线性代数库,用矩阵公式替代逐元素计算。哪怕是电商的计费系统,需要实时刻划账单总额,也不可能逐笔交易遍历,一定是预先按规则算好了公式模型。所有场景指向同一句问话,这也就是反方最终获胜的原因:“我需要检查每一个案例吗,还是可以直接算出答案?”
那晚,陈卓没有再去刷题,而是把那个辅助函数又看了一遍。它干的活极少,只负责一个数列的求和,但它像一面镜子:同样的需求,既可以演成一出遍历每一个数字的冗长戏剧,也可以压缩成三个乘法。差别不在数学本身,在于开工之前的那一秒停顿——你是在写循环,还是在找结构。数学家早就把3的倍数求和封装成了高斯求和公式,把重复计数问题抽象成了容斥原理,而程序员要做的,只是把它们翻译成可复用的函数。用一次是取巧,每次都这么想才是工程素养。
回头再看那行最初的代码,陈卓没有觉得它错,只是觉得它漏掉了一个更早的步骤。暴力循环是最后的保底手段,而不是第一反应。任何工程师面对“把符合条件的都算一遍”这种需求时,都应该先做三件事:认清模式(这里就是等差数列),抽取通用公式(高斯求和),再用组合法则消除重复(容斥原理)。顺序对了,复杂度可能就从 O(n) 跳变到 O(1)。这跟背更多算法无关,跟“计算代替计数”的思维有关。那一句“能不能算出来”比具体某个算法更值钱,它背后藏着一整套让软件快起来、让架构轻下来的直觉。
整个复盘最后凝结成了一条朴素的提醒:当你要遍历整个世界之前,先看一眼世界是不是早就把自己排列成了公式。如果是,就用乘法和除法回答它,别再用加法慢吞吞地走完每一步。
特别声明:以上内容(如有图片或视频亦包括在内)为自媒体平台“网易号”用户上传并发布,本平台仅提供信息存储服务。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.