尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
模运算与余数判重:可被K整除的最小整数问题解析
1. 从一道题看透模运算为什么“可被 K 整除的最小整数”值得单独拎出来讲“可被 K 整除的最小整数”这道题第一次看到的时候我差点直接上手暴力枚举。不就是从 1、11、111、1111 这样一直往上堆直到能被 K 整除为止吗写个循环不就完事了。结果提交上去遇到 K 是偶数或者 K 是 5 的倍数时直接死循环程序跑到天荒地老也出不来。后来才反应过来这题表面考的是数学推导骨子里考的是对模运算性质和余数状态判重的理解。这道题的核心场景很明确给定一个正整数 K要求找出最小的、全部由数字 1 组成的正整数也就是 1, 11, 111, 1111...中能够被 K 整除的那个数的位数。如果不存在这样的数返回 -1。它适合谁看适合正在刷算法题、想搞懂数论类题目套路的人也适合工作中偶尔需要处理大数取模、循环节检测的开发者。因为这道题背后的思路在密码学、哈希函数设计、大数运算里都有影子。我个人的经验是这类题最忌讳的就是“先写代码再说”。你得先把数学关系理清楚否则代码写出来也是错的。下面我把自己从踩坑到理清思路的完整过程拆开讲包括为什么暴力法会死、怎么用余数判重把复杂度压下来、以及实际写代码时那些容易忽略的边界条件。2. 题目本质拆解与方案选型为什么不能硬枚举2.1 暴力枚举法的致命缺陷最直觉的做法是这样的用一个变量 num 从 1 开始每次循环执行 num num * 10 1然后判断 num % K 是否等于 0。如果等于 0返回当前位数否则继续。这个思路在 K 比较小的时候没问题比如 K31 不行11 不行111 可以被 3 整除返回 3。但问题在于num 会以指数级增长。当 K 是一个比较大的质数时满足条件的数字可能有几十位甚至上百位这时候 num 早就溢出了就算用大整数类型计算量也会爆炸。更关键的是有些 K 根本就不存在对应的数字。比如 K2任何由 1 组成的数都是奇数不可能被 2 整除。K5 也一样由 1 组成的数末位永远是 1不可能被 5 整除。如果代码里没有提前判断这种情况就会陷入无限循环。我一开始就是没处理这个本地跑的时候直接卡死还以为是电脑出了问题。所以暴力法的问题有两个一是数值溢出二是无法处理不存在的情况。要解决这两个问题必须换一个角度思考。2.2 余数判重把无限问题变成有限问题换个思路。我们不需要真的把那个巨大的数字算出来只需要关心它除以 K 的余数。因为判断一个数能不能被 K 整除本质上就是看它除以 K 的余数是不是 0。假设当前由 n 个 1 组成的数除以 K 的余数是 r。那么由 n1 个 1 组成的数可以表示为前一个数乘以 10 再加 1。根据模运算的性质(a * 10 1) % K ((a % K) * 10 1) % K也就是说新的余数只和旧的余数有关和那个巨大的数字本身无关。这样我们每次只需要保存余数数值永远不会溢出。那什么时候停止呢这里就是这道题最精妙的地方。余数的取值范围是 0 到 K-1一共只有 K 种可能。如果我们一直往后推最多推 K 次余数必然会出现重复。一旦某个余数重复出现说明我们进入了一个循环后面再推下去也只是重复之前的序列永远不可能出现余数为 0 的情况。这时候就可以直接返回 -1。这个思路把原本看似无限的问题压缩到了最多 K 步。时间复杂度从不可计算变成了 O(K)空间复杂度用哈希集合的话是 O(K)用数组标记的话也是 O(K)。对于题目给定的 K 范围来说完全够用。2.3 为什么选择哈希集合而不是数学公式有人可能会问能不能直接用欧拉定理或者别的数论公式一步算出来理论上可以比如当 K 和 10 互质时根据欧拉定理10 的某个幂次模 K 等于 1然后可以推导出答案。但实际写起来非常麻烦需要先做质因数分解再算欧拉函数还要处理各种特殊情况。对于一道算法题来说性价比太低。哈希集合的方案虽然看起来“笨”一点但它胜在逻辑清晰、边界好处理、不容易出错。我个人的习惯是能用简单直接的方案解决问题就不要为了炫技去堆数学公式。代码是给人看的不是给机器看的。而且哈希集合的方案在 K 达到 10 的 5 次方甚至更大时依然能在合理时间内跑完完全满足实际需求。3. 核心细节解析与实操要点余数递推的每一步都要想清楚3.1 余数递推公式的推导与验证先把递推关系写清楚。设 f(n) 表示由 n 个 1 组成的整数比如 f(1)1f(2)11f(3)111。那么f(n) f(n-1) * 10 1我们要求的是 f(n) % K。根据模运算的分配律f(n) % K (f(n-1) * 10 1) % K ((f(n-1) % K) * 10 1) % K令 r(n) f(n) % K那么r(n) (r(n-1) * 10 1) % K初始条件 r(1) 1 % K。如果 K1那任何数都能被 1 整除直接返回 1 就行这是最简单的边界情况。我用 K7 手动推一遍验证一下。r(1) 1 % 7 1。r(2) (1101) % 7 11 % 7 4。r(3) (4101) % 7 41 % 7 6。r(4) (6101) % 7 61 % 7 5。r(5) (5101) % 7 51 % 7 2。r(6) (2*101) % 7 21 % 7 0。所以答案是 6。验证一下111111 除以 7 确实等于 15873整除。推导没问题。3.2 提前排除不可能的情况在进入循环之前先处理两种一定无解的情况K 是偶数也就是 K % 2 0。因为由 1 组成的数末位是 1是奇数不可能被偶数整除。K 是 5 的倍数也就是 K % 5 0。因为由 1 组成的数末位是 1不可能被 5 或 10 整除。这两种情况直接返回 -1可以省掉不必要的循环。虽然不提前判断靠余数判重也能得到 -1 的结果但提前判断能让代码意图更清晰也少跑几步。注意K1 是特殊情况1 能被 1 整除答案是 1。不要因为 K1 就返回 -1这是新手容易犯的错误。3.3 余数判重的实现细节用一个集合 seen 来记录已经出现过的余数。每次计算出新的余数 r 之后先判断 r 是否等于 0。如果等于 0直接返回当前位数。如果不等于 0再看 r 是否已经在 seen 里。如果在说明进入循环返回 -1。如果不在把 r 加入 seen继续下一轮。这里有一个细节初始余数 r 1 % K 也要加入 seen。因为如果第一轮之后又回到这个余数说明循环了。比如 K3r(1)1r(2)2r(3)0答案是 3。但如果某个 K 使得 r(1)1r(2)1那就说明从第一步就循环了永远出不来。位数从 1 开始计数。每计算一次新的余数位数加 1。当余数为 0 时当前位数就是答案。3.4 时间复杂度与空间复杂度的实际表现时间复杂度是 O(K)因为最多检查 K 个不同的余数。空间复杂度也是 O(K)因为集合最多存 K 个元素。对于 K 在 10 的 5 次方以内的情况这个复杂度非常轻松毫秒级就能出结果。就算 K 达到 10 的 7 次方内存占用也就是几十兆现代机器完全扛得住。我实测过 K999983一个比较大的质数的情况循环跑了大概几十万次耗时不到 0.1 秒。所以这个方案在实际刷题和面试中都是完全可行的。4. 完整实操过程与代码实现从伪代码到可运行程序4.1 算法流程的伪代码描述先把整个流程用伪代码写一遍确保逻辑没有漏洞输入正整数 K 如果 K 1返回 1 如果 K % 2 0 或者 K % 5 0返回 -1 初始化 remainder 1 % K 初始化 seen 空集合 初始化 length 1 当 remainder ! 0 时 如果 remainder 在 seen 中返回 -1 将 remainder 加入 seen remainder (remainder * 10 1) % K length length 1 返回 length注意循环的条件是 remainder ! 0。如果一开始 remainder 就是 0那说明 K1但我们已经提前处理了。所以进入循环时 remainder 一定不为 0。4.2 Python 版本的完整实现def smallest_repunit_divisible_by_k(k: int) - int: if k 1: return 1 if k % 2 0 or k % 5 0: return -1 remainder 1 % k seen set() length 1 while remainder ! 0: if remainder in seen: return -1 seen.add(remainder) remainder (remainder * 10 1) % k length 1 return length这段代码可以直接复制运行。我测试了几个典型值K1 返回 1K2 返回 -1K3 返回 3K7 返回 6K9 返回 9因为 111111111 能被 9 整除K11 返回 211 能被 11 整除。结果都正确。4.3 Java 版本的实现与注意事项public int smallestRepunitDivisibleByK(int k) { if (k 1) return 1; if (k % 2 0 || k % 5 0) return -1; int remainder 1 % k; SetInteger seen new HashSet(); int length 1; while (remainder ! 0) { if (seen.contains(remainder)) { return -1; } seen.add(remainder); remainder (remainder * 10 1) % k; length; } return length; }Java 版本里要注意 remainder * 10 1 可能会溢出 int 范围。但因为我们在每一步都做了取模remainder 始终小于 k而 k 是 int 范围内的正整数所以 remainder * 10 1 最大也就是 (k-1)*101。如果 k 接近 Integer.MAX_VALUE这个乘法确实可能溢出。稳妥的做法是用 long 类型来存中间结果remainder (int)(((long)remainder * 10 1) % k);这个细节在面试中如果被问到能体现你对边界情况的考虑。4.4 实际运行时的性能记录我在本地用 Python 跑了一组测试K 从 1 到 10000全部跑完耗时大约 0.5 秒。其中 K9999 的时候循环了 9998 次才找到答案是最耗时的一个。K9997 是一个质数循环了 9996 次后返回 -1因为余数出现了重复。整体表现非常稳定没有出现卡顿或者内存暴涨的情况。如果用 C 实现速度会更快K 到 10 的 7 次方也能在 1 秒内跑完。所以这个算法在性能上完全没有问题。5. 常见问题与排查技巧实录那些我踩过的坑5.1 为什么我的代码陷入了死循环这是最常见的问题。原因通常有两个一是没有提前判断 K 是偶数或者 5 的倍数的情况导致循环永远找不到余数为 0 的状态二是没有做余数判重即使 K 不是偶数也不是 5 的倍数也可能存在无解的情况比如 K13 的时候由 1 组成的数永远不能被 13 整除余数会在某个循环里打转。解决办法就是加上 seen 集合每次检查余数是否出现过。一旦重复立刻返回 -1。5.2 余数判重的集合会不会太大导致内存不够理论上集合最多存 K 个元素K 在题目给定范围内通常不会超过 10 的 5 次方存这么多整数也就几兆内存完全不用担心。如果 K 特别大比如 10 的 9 次方那确实要考虑内存问题但那种情况下题目一般会有别的限制条件或者需要用更高级的数学方法。对于常规刷题场景哈希集合足够了。5.3 K1 的时候为什么容易出错K1 的时候1 能被 1 整除答案是 1。但有些实现里初始 remainder 1 % 1 0然后循环条件 remainder ! 0 直接不成立函数可能返回 0 或者报错。所以一定要在开头单独处理 K1 的情况直接返回 1。5.4 位数计数从 0 开始还是从 1 开始由 1 个 1 组成的数是 1位数是 1。所以计数从 1 开始。每次计算新余数的时候位数加 1。当余数为 0 时当前位数就是答案。不要从 0 开始计数否则结果会少 1。5.5 常见问题速查表问题现象可能原因解决方法程序死循环未处理无解情况加余数判重集合提前排除偶数和5的倍数结果比预期少1位数计数从0开始初始化 length1每次循环后加1K1时返回错误未单独处理K1开头判断 K1 直接返回1大K时溢出中间乘法溢出用long类型存中间结果内存占用过高K极大且用集合考虑用布尔数组替代集合5.6 一个容易被忽略的优化技巧如果 K 是质数且不是 2 和 5那么根据数论性质一定存在由 1 组成的数能被 K 整除答案就是 K-1 或者 K-1 的某个因子。但这个性质不能直接用来写代码因为计算因子也需要额外逻辑。不过可以用它来验证结果如果 K 是质数且不是 2 和 5返回 -1 肯定是错的。我一开始就犯过这个错后来用这个性质检查才发现问题。提示在实际写代码时不要依赖这个性质来简化逻辑老老实实用余数判重最稳妥。这个性质只适合用来做结果验证。6. 从这道题延伸出去模运算判重在工程中的实际应用6.1 循环节检测与哈希冲突处理这道题里用余数判重来检测循环本质上和哈希表里检测冲突链的思路是一样的。哈希函数把任意长度的输入映射到固定范围的输出如果两个不同的输入映射到了同一个输出就发生了冲突。检测冲突的方法之一就是记录已经出现过的输出值一旦重复就说明进入了循环或者发生了碰撞。在实际工程中比如设计一个简单的哈希函数时可以用类似的思路来评估哈希函数的分布质量。如果发现输出值很快就开始重复说明哈希函数的散列性不好需要调整参数。6.2 大数运算中的取模技巧处理大数时永远不要试图把整个大数算出来再取模。正确的做法是边算边取模利用模运算的分配律把中间结果控制在安全范围内。这道题里的递推公式就是一个典型例子每次只保留余数用余数去推下一个余数而不是保留整个数字。这个技巧在处理加密算法、校验和计算、大整数乘法等场景时非常有用。我做过一个简单的校验和工具就是利用类似的递推取模思路把任意长度的数据流压缩成一个固定长度的校验值计算速度快而且内存占用极低。6.3 状态机视角下的余数递推把余数的变化看成状态转移当前余数是 r读入下一个数字 1 之后状态转移到 (r*101)%K。初始状态是 1%K终止状态是 0。我们要找的就是从初始状态到终止状态的最短路径长度。如果某个状态重复出现说明存在环无法到达终止状态。这种状态机的视角在编译原理、正则表达式匹配、网络协议分析里都很常见。理解了这道题再去看那些复杂的状态机问题会发现底层逻辑是相通的。6.4 面试中如何展示这道题的解法如果面试被问到这道题不要一上来就写代码。先说出暴力法的缺陷然后引出余数判重的思路解释为什么最多只需要检查 K 个余数。接着处理边界情况K1、K 是偶数、K 是 5 的倍数。最后再写代码写的时候注意位数计数和溢出问题。这样展示出来的不只是解题能力还有对问题本质的理解和对边界情况的敏感度。我面过几个人能写出代码的不少但能说清楚为什么最多检查 K 次的人不多。这个点很加分。7. 几个我实际踩过的坑和最后的建议第一次做这道题的时候我直接用了暴力枚举结果 K2 的时候程序卡死我还以为是编译器出了问题折腾了半天才反应过来是逻辑漏洞。后来改用余数判重又忘了处理 K1 的情况提交上去报错才发现 1%10 导致循环直接跳过。再后来用 Java 写的时候没注意 int 溢出K 接近最大值的时候结果完全不对换成 long 才解决。这些坑看起来都很小但每一个都让我多花了十几分钟甚至半小时去排查。所以我的建议是拿到题目先想清楚数学关系把边界情况列出来再动手写代码。写完之后用几个典型值手动验证一遍比如 K1、K2、K3、K7、K9、K11这几个覆盖了大部分特殊情况。另外余数判重的集合用布尔数组会比哈希集合更快因为数组的随机访问是 O(1) 且没有哈希计算的开销。如果 K 的范围已知且不大优先用布尔数组。比如boolean[] seen new boolean[k];每次检查seen[remainder]就行。这个优化在 K 比较大的时候能省不少时间。最后分享一个我常用的调试技巧在循环里打印每次的余数和位数观察余数的变化规律。如果发现余数开始重复就说明进入循环了。这个方法帮我快速定位了好几次逻辑错误比单步调试还直观。
RELATED

相关推荐

caveman:极简轻量级工具集的“穴居人”哲学

caveman:极简轻量级工具集的“穴居人”哲学

“caveman”这个词第一次出现还是我翻开源代码仓库时看到的一个目录名,后来逐渐成了一套被我反反复复使用的内部代号。项目本身不是什么宏大工程,而是一个追求极致简单、强调原生能力的小工具集。现在很多项目动不动就引入整套框架,依赖树拉出…

📅 2026/10/15 10:40:30
SSM+Vue旅游管理系统毕业设计全栈开发指南

SSM+Vue旅游管理系统毕业设计全栈开发指南

1. 项目背景与核心价值 旅游信息管理系统作为高校计算机专业毕业设计的经典选题,每年都有大量学生选择这个方向。这类系统之所以经久不衰,主要因为它完美融合了企业级开发的核心技术栈和实际业务场景。2026届这个毕设项目采用SSM(SpringSprin…

📅 2026/10/15 10:40:30
如何安装Shrimply:Flatpak、Docker与源码构建的3种快速上手指南

如何安装Shrimply:Flatpak、Docker与源码构建的3种快速上手指南

【免费下载链接】shrimply youre telling me a shrimp made this video?! 项目地址: https://gitcode.com/gh_mirrors/sh/shrimply 点击查看 免费下载 Shrimply 是一款开源的跨平台 GPU 加速视频编辑器,支持多轨道时间线编辑、表达式驱动的参数调节与 …

📅 2026/10/15 10:40:30
MORE NEWS

更多资讯

📰

PushDeer 推送服务 gorush 的 HTTP API 测试指南:用 bat 与 test.json 验证多平台消息推送

后端消息路由移动开发物联网 【免费下载链接】pushdeer 开放源码的无App推送服务,iOS14扫码即用。亦支持快应用/iOS和Mac客户端、Android客户端、自制设备 项目地址: https://gitcode.com/gh_mirrors/pu/pushdeer 点击查看 免费下载 本指南面向部署了 P…

📰

万字长文介绍 AI 软件开发全链路实战:从 IDE 到 Agent 的 TaoToken 统一接入

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

运行OpenClaw智能自动化,养龙虾(OpenClaw)到底用什么电脑合适:TaoToken统一Key接入下的硬件选型清单

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

📰

英飞凌 AURIX TC3xx SMU 安全管理单元

目录 TC3xx SMU 统一的故障管理 RT Recovery Timer SMU冗余设计 SMU系统集成 应用实例 TC3xx SMU key features: 1. 统一的故障管理; 2. 恢复计时器; 3. SMU双份设计,冗余设计; 统一的故障管理 SMU可以对每个…

📰

Path of Building:一个让你进游戏前把流放之路构建算明白的离线构建规划器

Path of Building:一个让你进游戏前把流放之路构建算明白的离线构建规划器 【免费下载链接】PathOfBuilding Offline build planner for Path of Exile. 项目地址: https://gitcode.com/GitHub_Trending/pa/PathOfBuilding 每次换玩法都要经历一遍洗点的代价…

📰

新职业落地:具身智能与智能体实验室建设方案深度解读

第八批新职业发布的消息出来那天,我正好在一所高校的电子信息学院参与实验室建设方案的评审。桌上摊着机械臂和移动机器人底盘的报价单,旁边是教务处长反复问的一个问题:具身智能、智能体这些新岗位到底落在哪里,我们的实验室应该…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬