news 2026/10/1 12:02:36

同余模运算巧解:只改个位凑出7的倍数

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
同余模运算巧解:只改个位凑出7的倍数

前几天在一个程序员闲聊群里看到一道题,题目就一句话:“简单修改一个n,让它变成7的倍数”。说实话,第一眼看到这题我是有点懵的——修改一个n?n是个变量还是某个具体数字?怎么个改法?后来大家七嘴八舌一讨论,问题才逐渐变得清晰:任给你一个正整数n,能不能通过改动它的某一位数字,让新得到的数能被7整除?而最漂亮的结论是——只改个位,永远够用。无论n长什么样,你都能只调整个位数字,把它变成7的倍数。这个结论并不复杂,但背后的同余思想特别耐嚼,今天就从这道题出发,把证明过程、适用范围和实际用处一次聊透。

1. 一个没头没尾的题,先把它翻译成人话

1.1 “修改一个n”到底允许动哪里

这类一句话题目在数学趣题和算法群里特别常见,最坑人的地方不是解法,而是定义不清。第一次读完题目后,我先把“修改一个n”补全成自己认为最合理的版本:

任给一个正整数 n,允许把它的十进制表示中某一位数字改成 0 到 9 中的另一个数字(改动后最高位不能为 0),问:是否总能得到一个 7 的倍数?

如果 n 本身已经是 7 的倍数,那题目已经完成,所以真正要回答的是“n 不是 7 的倍数时,改一位能不能成功”。讨论中大家还发现了一个更强的版本:不只改一位能成功,甚至把所有其他位都锁死、只允许改个位,也一定能成功。

先看两个具体例子。n=123,个位是3,把3改成6,得到126=7×18。n=2024,个位是4,把4改成3,得到2023=7×289。两个例子都只动了最后一位。

一位数的情况比较特殊,比如 n=1,把个位1改成7,得到7,是7的倍数;n=5 也一样,改成7就行。所以绝大多数一位数都能通过改个位完成。但 n=7 本身已经是7的倍数,如果题目强制要求“必须改一位且结果还是正整数”,那就没有解了;这个边界细节后面专门讨论。

1.2 7为什么是这道题的“主角”

选7不是随机的。小学阶段我们学过很多整除判定:2和5看末位,3和9看各位数字和,4看末两位,8看末三位,11看奇数位与偶数位的差。但7一直没有课本级的简便口诀。原因在于,7在十进制底下“藏得很深”:10 对 7 取余是 3,10² 对7取余是2,10³ 对7取余是6,10 的幂模7的周期长达6位,不像2和5那样只看最后一位就完事。

正是因为7没有一望即知的口诀,它成了各种趣味数学题和算法题最爱的“硬骨头”:判断一个大数能不能被7整除,真的需要做除法或者走截尾法循环。反过来,“凑一个7的倍数”这个问题也因此变得有意思——如果题目换成“凑2的倍数”,答案太显然,改个位为偶数就行,根本不需要证明;换成“凑3的倍数”,可以调各位数字和,但复杂得多。7恰好卡在一个“直接改个位就能成、但大多数人一时想不到”的位置上。

1.3 直觉上的第一反应:改十位行不行

很多人第一反应是改最高位,比如把2024改成1424或3424这种比较大的变化,但验算下来都不靠谱。实际上,改十位不一定总能成功,原因是十位数字变化等价于整体加上一个 k×10,而 10 在模7下的变化只覆盖一部分余数,不够灵活。

真正能保证成功的,是直接改个位。为什么是它?因为个位的变化可以等间隔地覆盖0到9十个数字,而模7一共只需要7类余数,十有八九能命中。这个直觉在下一节会变成严格的证明。现在先记住一个方向:末位才是“万能拧手”。

2. 只改个位,为什么永远都能凑出7的倍数

2.1 把“改个位”翻译成一条同余式

设原数为 n,去掉个位后是 q,个位数字为 d,于是

n = 10q + d

我们的操作是把 d 换成某个 x ∈ {0,1,...,9},得到新数 n′ = 10q + x。

目标:n′ 能被7整除,也就是

10q + x ≡ 0 (mod 7)

又因为 n ≡ 10q + d (mod 7),移项得到 10q ≡ n - d (mod 7),代入目标式:

x ≡ d - n (mod 7)

到这里,问题已经从“找一个数”变成了“找一个余数”。需要的 x 不是一个天外飞仙,它只是一个同余类的代表元。接下来就看这个代表元能不能落在0到9之间。

2.2 需要的余数恰好落在0到6之间

设 r = (d - n) mod 7,按惯例取 0 ≤ r < 7。关键点来了:r 的取值范围正好就是 {0,1,2,3,4,5,6},而这七个数字每一个都是合法的个位数字。所以直接令 x = r,就一定能落在0-9范围内。这就是证明的全部秘密。

至于0到9多出来的7、8、9三个数字,它们会制造“第二个解”。下表列出 x 从0到9时与7的模关系:

新个位 x0123456789
x mod 70123456012

如果算出来的 r=0,那么 x=0 或 x=7 都能用;r=1,则 x=1 或 8;r=2,则 x=2 或 9。剩下的 r=3,4,5,6 时,r+7 已经超过9,就只有一个解。

这里有个值得注意的细节:0算不算7的倍数?按数学定义,0=0×7,当然是7的倍数。所以在允许结果为0的场合,x=0是一个合法选择;如果题目要求最终必须是一个正整数,那就得避开0。

2.3 完整证明:任意n都能只靠改个位完成

把上面的推理整理成三段式:

  1. 分解 n=10q+d,改个位为 x,得到 n′=10q+x。
  2. 根据同余的加减性,n′≡0 (mod 7) 等价于 x≡d-n (mod 7)。
  3. 令 r 是 d-n 除以7的余数(0≤r<7),那么 r 本身就是0到6之间的数字,新数个位取 x=r,即可保证被7整除。

证明到这里已经完整。它没有用到 n 的任何额外性质,因此是“任意 n 都成立”的结论。更妙的是,它甚至不需要真的算出 n′ 有多大——只要知道了 n 除以7的余数和它的个位数字,r 就确定了。

做一个实例验证:n=31415926。先算 31415926 mod 7。用纸笔逐步取模:4487989×7=31415923,所以 31415926=31415923+3,余数是3。个位 d=6,于是 r=(6-3)≡3 (mod 7),把个位6改成3,得到31415923。验证:31415923÷7=4487989,确实整除。

如果 n=7 这种边界,按这个公式走:d=7,n mod 7=0,r=(7-0)≡0,候选解是 x=0(得到0)或 x=7(等于没改)。如果把“0是7的倍数”也放进来,那么把7改成0也算一种答案,只是通常讨论正整数时不算。这个约定问题,做算法题时一定要在开头问清楚。

3. 从7到任意模数:成立的边界在哪里

3.1 模数不超过10时,个位修改法必可行

把“7”换成任意 m≤10 的正整数,证明几乎不用变:需要的 x 满足 x≡d-n (mod m),令 r=(d-n) mod m,则 0≤r<m≤10,而 r 一定在0-9内,所以直接取 x=r 即可。

举例说明:

  • 改成2的倍数:只需要把个位调成偶数。比如17,个位7改成0、2、4、6、8中的任一个,得到10、12、14、16、18,全是2的倍数。
  • 改成5的倍数:个位调成0或5。
  • 改成3的倍数:个位也能永远调成功。比如40,40 mod 3=1,d=0,r=(0-40) mod 3 = 1? 实际算一下:40 mod 3=1,0-1=-1≡2,所以 r=2,把个位0改成2,得到42=3×14。
  • 改成10的倍数:个位改成0即可,当然要考虑原数首位不为0的限制。

这里要特别说明:m≤10这个条件重点在于“余数集合大小不超过个位可选数字的个数”,和 m 与 10 是否互质没关系。改成4的倍数也一样,r 在0-3,取 x=r 即可。

3.2 模数超过10之后,反例开始出现

当 m>10,情况就变了。需要的余数 r 可能落在10到m-1之间,而个位只能提供0-9,于是找不到对应的 x。

最直接的反例是 m=11,n=11。n本身是11的倍数;如果允许不改,题目已完成。但如果强制必须修改个位,把个位1改成0,2,3,...,9,得到的10,12,13,...,19对11取余分别是10,1,2,...,8,没有一个是0。所以“必须改个位且必须改掉原数字”时,11这个数永远凑不成11的倍数。

m=12也是一样,n=12强制改个位无解。更深一层的原因是:模数有12种余数,个位只有10种变化,可选的数字少两个,遇到缺的那两个余数就必然失败。

3.3 加个“只能变大/只能变小”的约束,结论就崩了

原题没说不准变大还是变小,所以 x 既可以比 d 小也可以比 d 大。但现实中经常有人把题目记错成“只能改大一位”或者“只能改小一位”,这时候定理就不成立了。

  • 只允许把个位改大:n=7,个位7只能改成8或9,8和9都不是7的倍数,失败。
  • 只允许把个位改小:n=7,个位7改成0到6。如果要求正整数结果,0不能用,1到6也都不是7的倍数,失败。
  • 不限制方向,但强制“结果不能等于原数”:n=7依然是问题元凶,因为唯一候选x=7恰好等于没改,x=0又会被某些人排除。

这些边界再一次说明:一道好的趣题,往往不是难在中间的证明,而是难在开头那句约定没写清楚。实际做这类问题,我会先把“修改”的定义钉死:改几位?限制方向吗?结果允不允许0?然后再往下推。

4. 手算和代码:最快找到那个应改的数字

4.1 手算套路:先取模,再倒推个位

如果不想写代码,标准流程是:

  1. 写出 n 的个位 d,其余部分记为 q。
  2. 计算 r=(d-n) mod 7,也就是对 n 取模后拿个位减掉它,再取余数。
  3. 新个位优先取 x=r;如果 r 恰好等于原个位 d,且题目要求必须改动,就考虑 x=r+7(前提是不超过9)。
  4. 验证 n′=10q+x 是不是7的倍数。

两个手算例子。n=2024,2024 mod 7=1,d=4,r=3,个位改成3,得2023。验证:2023=7×289。

n=987654,先算它对7的余数:7×141093=987651,987654比它大3,所以余数是3。个位 d=4,r=(4-3)≡1,新个位可以取1或8,所以候选是987651和987658。前者是7×141093,后者是7×141094。这个例子展示了“r=1时有两个解”的情况:一个改小3,一个改大4,看业务上更喜欢哪个方向。

4.2 Python实现:只改个位与穷举任意位

直接给代码。第一个函数只改个位,把得到的候选全部列出来:

def fix_last_digit(n: int, m: int = 7) -> list[int]: q, d = divmod(n, 10) r = (d - n) % m ans = [] for k in range(10): x = r + k * m if x <= 9: ans.append(q * 10 + x) return ans

跑几个用例:

print(fix_last_digit(2024)) # [2023] print(fix_last_digit(987654)) # [987651, 987658] print(fix_last_digit(31415926)) # [31415923] print(fix_last_digit(123)) # [126]

如果允许修改任意一位,想找“改动后离原数最近”的7的倍数,可以用一段暴力枚举:把每一位都试着改成0-9,计算差值绝对值,保留最小。

def nearest_multiple_any_digit(n: int) -> int: if n % 7 == 0: return n s = list(str(n)) best = None best_diff = None for i in range(len(s)): for ch in '0123456789': if ch == s[i]: continue t = int(''.join(s[:i] + [ch] + s[i+1:])) if t % 7 == 0: diff = abs(t - n) if best is None or diff < best_diff: best, best_diff = t, diff return best

这段代码能生成“允许改任意一位的最优解”,满足一些把题理解为“改一位让它离原数最近”的需求。注意它默认不允许改最高位为0,并且要求至少改动一位;如果 n 本身就是7的倍数,它直接返回 n。

4.3 如果题目要求“改动尽量小”怎么处理

刚才的暴力枚举能解决“任意位、改动最小”的版本。但如果只允许改个位,“改动最小”就是在两个候选(r 和 r+7,若 r+7≤9)之间挑一个使 |x-d| 最小的。

举例:n=987654 时,候选是 x=1 和 x=8,原个位 d=4,差值分别是3和4,所以选 x=1,改后是987651,只差3。

更一般地,改个位造成的偏差最多不会超过9,但实际因为 r 在0-6之间,x 又只能等于 r 或 r+7,所以候选值相对原个位通常偏离很小。这也是为什么“只改个位”在工程里特别好用:它把搜索空间压到了常数级,一个同余式子就出答案。

5. 这题背后的同余思想,其实到处都是

5.1 校验码:ISBN和身份证里的“7的倍数”

“补一个数让整体能被 k 整除”这个动作,在现实世界中叫校验码。ISBN-10 的校验位设计就是前9位数字加权求和,补上第10位让加权和对11取模为0。身份证号码的最后一位校验码也是加权求和、模11取余算出来的。它们使用的都是同一套工具:模运算 + 反推缺失位。

回到我们的题目:给一个数 n,改个位让它成为7的倍数,本质上就是“让个位这个自由变量去补足 10q 对7的缺口”。这种“留一个自由位、其他位固定、按模运算反推自由位”的思路,是很多编码和校验系统的底层逻辑。

5.2 7的整除速判口诀是怎么来的

很多人见过这个口诀:去掉个位,剩下数减去个位的两倍,反复进行,最后能判断原数是不是7的倍数。它为什么对?设原数 N=10q+d,口诀算的是 q-2d。我们有:

10q+d = 10(q-2d)+21d

而 21d 显然是7的倍数,所以 N≡0 (mod 7) 当且仅当 10(q-2d)≡0 (mod 7)。又因为10与7互质,可以安全地把10从同余式里消掉,于是等价于 q-2d≡0 (mod 7)。

这个推导和本文“改个位”的推导用的是同一条同余桥:10q+d≡0 (mod 7)。可以说,“7的倍数”这个看似没什么规律的主题,在同余视角下其实是很有规律的。

5.3 对写代码的人:生成“对齐数”的通用思路

日常写测试用例、造数据时,经常要“把某个随机数对齐到某个步长”。比如时间戳对齐到15分钟、文件大小对齐到4KB、股票数量对齐到100股等等。

最常用的写法是向上取整:

def align_up(n: int, m: int) -> int: return ((n + m - 1) // m) * m

而本文的“改个位凑倍数”适合另一种约束:只允许动最末一位。比如某个协议里最后一位是校验位,前面若干位是业务数据,那么计算校验位时就会用到公式 r=(d-n) mod m。这本质上和“随机数对齐”是同一套心法,只不过约束条件更苛刻。

回头看,这题真正难的地方不在证明,而在把“修改一个n”这句话想清楚。我第一次拿到题时,先是懵了一会儿为什么非得是7,然后列了一堆枚举方案。当发现只改个位就永远够的时候,我的第一反应是:那还枚举什么?一道题,先找自由变量,再算余数,最后反推答案——这个三步走比任何暴力搜索都快得多,也更优雅。以后我再遇到“把一个数变成X的倍数”的需求,我的默认检查顺序会是:允许动哪一位?有没有方向限制?容不容忍0?这三个条件一旦定下来,同余式往纸上一摆,答案自己就漏出来了。

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

315百度万词霸屏解析:从流量幻觉到内容资产

每年3月15日前后&#xff0c;做百度的同行朋友圈总会炸开锅。有人上一秒还在晒"某个冷门词又上首页了"&#xff0c;下一秒就发现整站流量断崖式归零。这个时间节点&#xff0c;和"315百度万词霸屏"这个玩法有着绕不开的关联。万词霸屏&#xff0c;说白了就…

作者头像 李华
网站建设 2026/10/1 12:02:13

PAT顶级真题题解:字符串哈希、拆点最短路、树形DP与线段树倒置

三月底的机房里&#xff0c;我看到第四题题面第一行写着"维护一个01序列"的时候&#xff0c;就知道这场的顶级大概率是场硬仗。2024年春季的攀拓&#xff08;PAT&#xff09;顶级考试&#xff0c;四道题分别落在字符串哈希、分层图最短路、树形DP方案数、线段树区间倒…

作者头像 李华
网站建设 2026/10/1 12:02:11

全流程电池测试解决方案:从电芯到PACK的判定链路与落地细节

几年前&#xff0c;我遇到过一单印象特别深的退货分析&#xff1a;整批储能PACK在客户端报“充电电流异常”&#xff0c;退回工厂后单独测电芯&#xff0c;容量、内阻、自放电全部合格&#xff1b;单独测模组&#xff0c;压差、绝缘也都正常。前前后后折腾了一周&#xff0c;最…

作者头像 李华
网站建设 2026/10/1 12:00:50

MacOS 安装 JDK8 完整指南:发行版选择、环境变量与多版本共存

1. 为什么现在的 Mac 上还留着 JDK8 的位置 聊 MacOS 下载安装 JDK8 这件事&#xff0c;本身就带着一点“逆时代”的味道。毕竟 JDK 已经走到 20 多个版本&#xff0c;LTS 都换了好几轮&#xff0c;随便一个新项目脚手架拉下来&#xff0c;默认都是 17 或者 21。但只要你接过维…

作者头像 李华