力扣1015C++解法技巧

核心思路与数学原理

本题要求找到仅由数字 1 组成的最小正整数 N,使得 N 能被给定的正整数 K 整除,并返回 N 的位数。若不存在这样的 N,则返回 -1 。

1. 无解情况判定

若 K 是偶数(即 K % 2 == 0)或 K是 5 的倍数(即 K % 5 == 0),则不存在仅由 1 组成且能被 K 整除的数。这是因为任何仅由 1 组成的数(如 1, 11, 111...)其个位永远是 1,而能被 2 或 5 整除的数个位必须是 0, 2, 4, 5, 6, 8,因此直接返回 -1 。

2. 鸽巢原理(抽屉原理)优化

若 K 与 10 互质(即非 2 和 5 的倍数),解一定存在。我们不需要构造巨大的整数 N,而是通过模拟除法过程计算余数。

设 R_i 为由 i 个 1 组成的数对 K 取模的结果。递推公式为:

R_i = (R_{i-1} \\times 10 + 1) \\pmod K

其中 R_0 = 0。

根据鸽巢原理,余数的可能取值范围是 \[0, K1\]。如果在 K 次迭代内余数仍未变为 0,则必然出现重复的余数,意味着进入了无限循环,此时可判定无解(但在本题数学特性下,只要 K 不含因子 2 和 5,必在 K 次内找到解)。因此,循环次数上限为 K。

3. 避免溢出

直接使用 long long 存储全 1 数会在位数较多时溢出。必须在每一步计算后立即对 K 取模,保持数值始终小于 K 。

C++ 代码实现

cpp 复制代码
#include <iostream>
#include <unordered_set>

class Solution {
public:
    int smallestRepunitDivByK(int k) {
        // 若 k 是偶数或 5的倍数,个位为 1 的数永远无法被整除,直接返回 -1
        if (k % 2 == 0 || k % 5 == 0) {
            return -1;
        }

        int remainder = 0; // 当前余数
        // 根据鸽巢原理,最多尝试 k 次即可找到循环节或解 for (int length = 1; length <= k; ++length) {
            // 核心递推公式:新余数 = (旧余数 * 10 + 1) % k
            // 此操作避免了大数溢出,始终保持 remainder < k
            remainder = (remainder * 10 + 1) % k;

            // 若余数为 0,说明当前的 length 即为所求的最小位数
            if (remainder == 0) {
                return length;
            }
        }

        // 理论上若 k 不含 2 和 5 因子,必在上述循环内返回;此处为防御性编程
        return -1;
    }
};

// 示例主函数用于本地测试
int main() {
    Solution sol;
    int k1 = 1;
    int k2 = 2;
    int k3 = 3;
 int k4 = 7;

    std::cout << "K=" << k1 << ", Length: " << sol.smallestRepunitDivByK(k1) << std::endl; // 输出 1 (1%1==0)
    std::cout << "K=" << k2 << ", Length: " << sol.smallestRepunitDivByK(k2) << std::endl; // 输出 -1 (偶数)
    std::cout << "K=" << k3 << ", Length: " << sol.smallestRepunitDivByK(k3) << std::endl; // 输出 3 (111%3==0)
    std::cout << "K=" << k4 << ", Length: " << sol.smallestRepunitDivByK(k4) << std::endl; // 输出 6 (111111%7==0)

    return 0;
}

算法复杂度分析

指标 分析结果 说明
时间复杂度 O(K) 最坏情况下需要遍历 K 次余数状态,每次计算为 O(1) 。
空间复杂度 O(1) 仅使用了几个整型变量存储余数和计数器,未使用额外数组或哈希表(利用数学性质隐式去重)。
数据范围 支持 K \\le 10\^5 由于避免了大数运算,该算法能高效处理 LeetCode 规定的 K 的最大值。

关键步骤解析

  1. 预处理判断 :首先检查 k % 2 == 0 或 k % 5 == 0。这是基于数论性质的快速剪枝,因为 10x + 1 形式的数不可能被 2 或 5 整除 。
  2. 模运算递推 :利用 (remainder * 10 + 1) % k 模拟在末尾添加数字 1的过程。例如,从 1 到 11,数学上是 1 \\times 10 + 1;从 11 到 111,是 11 \\times 10 + 1。通过对 K 取模,我们将数值控制在整数范围内 。
  3. 循环终止条件:循环上限设为 K。如果在 K 次内没有找到余数为 0 的情况,根据抽屉原理,余数必然重复,后续将进入死循环,故可判定无解(尽管对于互质的 K,解必然存在)。

参考来源

相关推荐
seowmt1 小时前
GEO效果该什么时候看数据:从立项到复盘的时间线复盘
算法·ip·seo·geo·faq
悠仁さん1 小时前
【算法C++】双指针(一)
数据结构·c++·算法
文丑颜不良啊1 小时前
LeetCode626~650题目解析
java·leetcode
jimy11 小时前
lvalue、xvalue、prvalue(二):引用是哪一类?
开发语言·c++·1024程序员节
小豆庄1 小时前
本地视频脱敏的追踪系统踩坑——车牌过滤、转场误判与ID冲突排查
人工智能·算法·目标检测·音视频·视频·敏捷流程·纹理贴图
忆挽篱笙歌1 小时前
进程替换函数与shenll命令行解释器
前端·javascript·算法
YukiRinLL1 小时前
字符串存储与 BF、KMP 匹配算法
数据结构·算法
小超人诞生日记1 小时前
Linux:环境变量 & 进程虚拟地址空间
linux·算法
杜 硕1 小时前
从零开始的C++生活——第三讲
开发语言·c++·生活