给定正整数 k
,你需要找出可以被 k
整除的、仅包含数字 **1**
的最 小 正整数 n
的长度。
返回 n
的长度。如果不存在这样的 n
,就返回-1。
注意: n
可能不符合 64 位带符号整数。
示例 1:
**输入:** k = 1
**输出:** 1
**解释:** 最小的答案是 n = 1,其长度为 1。
示例 2:
**输入:** k = 2
**输出:** -1
**解释:** 不存在可被 2 整除的正整数 n 。
示例 3:
**输入:** k = 3
**输出:** 3
**解释:** 最小的答案是 n = 111,其长度为 3。
提示:
问题提示 如何通过遍历找到能被 k 整除的最小正整数 n 的长度? 如何在遍历过程中判断出现循环并避免重复计算? 如何将原问题转化为应用该算法或定理的形式?
前置知识
理解正整数、整除、余数的概念及其运算法则。
掌握哈希表的基本操作。
数论基础知识,包括欧拉定理和取模运算等。
解题思路 方法一:遍历 思路与算法
题目要求出长度最小的仅包含的 1 的并且被 k 整除的正整数。我们从 n = 1 开始枚举,此时对 k 取余得余数 resid} = 1 \bmod k。如果 resid 不为 0,则表示 n 当前还不能被 k 整除,我们需要增加 n 的长度。令 n_{new} = n_{old} \times 10 + 1,resid}{new} = n {new} \bmod k。将 n_{old 代入其中可得:
\begin{aligned} \textit{resid}{new} &= (n {old} \times 10 + 1) \bmod k \ &= ((n_{old} \bmod k) \times 10 + 1) \bmod k \ &= (\textit{resid}_{old} \times 10 + 1) \bmod k \end{aligned}
从上式可以发现,新的余数 resid}{new 可以由 resid} {old 推导得到。因此在遍历过程中不需要记录 n,只需记录 resid。由于 resid 是对 k 取余之后的余数,因此种类数不会超过 k。
在遍历过程中如果出现重复的 resid,表示遇到了一个循环,接着遍历下去会重复循环中的每一步,不会产生新的余数。所以我们用一个哈希表记录出现过的余数,当更新 resid 后发现该值已经在哈希表时,直接返回 -1。否则我们一直遍历,直到 resid 变为 0。最终哈希表中的元素个数或者遍历次数就是实际 n 的长度。
代码
[sol1_1-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 class Solution {public : int smallestRepunitDivByK (int k) { int resid = 1 % k, len = 1 ; unordered_set<int > st; st.insert (resid); while (resid != 0 ) { resid = (resid * 10 + 1 ) % k; len++; if (st.find (resid) != st.end ()) { return -1 ; } st.insert (resid); } return len; } };
[sol1_1-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Solution { public int smallestRepunitDivByK (int k) { int resid = 1 % k, len = 1 ; Set<Integer> set = new HashSet <Integer>(); set.add(resid); while (resid != 0 ) { resid = (resid * 10 + 1 ) % k; len++; if (set.contains(resid)) { return -1 ; } set.add(resid); } return len; } }
[sol1_1-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 public class Solution { public int SmallestRepunitDivByK (int k ) { int resid = 1 % k, len = 1 ; ISet<int > set = new HashSet<int >(); set .Add(resid); while (resid != 0 ) { resid = (resid * 10 + 1 ) % k; len++; if (set .Contains(resid)) { return -1 ; } set .Add(resid); } return len; } }
[sol1_1-C] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 typedef struct { int key; UT_hash_handle hh; } HashItem; HashItem *hashFindItem (HashItem **obj, int key) { HashItem *pEntry = NULL ; HASH_FIND_INT(*obj, &key, pEntry); return pEntry; } bool hashAddItem (HashItem **obj, int key) { if (hashFindItem(obj, key)) { return false ; } HashItem *pEntry = (HashItem *)malloc (sizeof (HashItem)); pEntry->key = key; HASH_ADD_INT(*obj, key, pEntry); return true ; } void hashFree (HashItem **obj) { HashItem *curr = NULL , *tmp = NULL ; HASH_ITER(hh, *obj, curr, tmp) { HASH_DEL(*obj, curr); free (curr); } } int smallestRepunitDivByK (int k) { int resid = 1 % k, len = 1 ; HashItem *st = NULL ; hashAddItem(&st, resid); while (resid != 0 ) { resid = (resid * 10 + 1 ) % k; len++; if (hashFindItem(&st, resid) != NULL ) { hashFree(&st); return -1 ; } hashAddItem(&st, resid); } hashFree(&st); return len; }
[sol1_1-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 var smallestRepunitDivByK = function (k ) { let resid = 1 % k, len = 1 ; const set = new Set (); set.add (resid); while (resid != 0 ) { resid = (resid * 10 + 1 ) % k; len++; if (set.has (resid)) { return -1 ; } set.add (resid); } return len; };
优化
注意到当 k 为 2 或者 5 的倍数时,能够被 k 整除的数字末尾一定不为 1,所以此时一定无解。
那当 k 不为 2 或者 5 的倍数时一定有解吗?我们做进一步的分析。
resid 随着 1 的增加,最后一定进入循环,我们能找到两个对 k 同余的 n 和 m。假设 n \gt m,那么一定有以下等式成立:
(n - m) \equiv 0 \pmod k
n - m 可以表示为 11\dots 100\dots 0 的形式,因此有 11\dots 100\dots 0 \equiv 0 \pmod k。
如果此时 k 不为 2 或 5 的倍数,则 k 与 10 没有公因数,k 与 10 互质。n - m 末尾的 0 可以除掉,因此 11\dots 1 \equiv 0 \pmod k,问题一定有解。
[sol1_2-C++] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 class Solution {public : int smallestRepunitDivByK (int k) { if (k % 2 == 0 || k % 5 == 0 ) { return -1 ; } int resid = 1 % k, len = 1 ; while (resid != 0 ) { resid = (resid * 10 + 1 ) % k; len++; } return len; } };
[sol1_2-Java] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Solution { public int smallestRepunitDivByK (int k) { if (k % 2 == 0 || k % 5 == 0 ) { return -1 ; } int resid = 1 % k, len = 1 ; while (resid != 0 ) { resid = (resid * 10 + 1 ) % k; len++; } return len; } }
[sol1_2-C#] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class Solution { public int SmallestRepunitDivByK (int k ) { if (k % 2 == 0 || k % 5 == 0 ) { return -1 ; } int resid = 1 % k, len = 1 ; while (resid != 0 ) { resid = (resid * 10 + 1 ) % k; len++; } return len; } }
[sol1_2-C] 1 2 3 4 5 6 7 8 9 10 11 12 int smallestRepunitDivByK (int k) { if (k % 2 == 0 || k % 5 == 0 ) { return -1 ; } int resid = 1 % k, len = 1 ; while (resid != 0 ) { resid = (resid * 10 + 1 ) % k; len++; } return len; }
[sol1_2-Python3] 1 2 3 4 5 6 7 8 9 10 11 class Solution : def smallestRepunitDivByK (self, k: int ) -> int : if k % 2 == 0 or k % 5 == 0 : return -1 ans, resid = 1 , 1 while resid % k != 0 : resid = (resid % k) * (10 % k) + 1 ans += 1 return ans
[sol1_2-JavaScript] 1 2 3 4 5 6 7 8 9 10 11 12 var smallestRepunitDivByK = function (k ) { if (k % 2 === 0 || k % 5 === 0 ) { return -1 ; } let resid = 1 % k, len = 1 ; while (resid !== 0 ) { resid = (resid * 10 + 1 ) % k; len++; } return len; };
[sol1_2-Golang] 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 func smallestRepunitDivByK (k int ) int { if k % 2 == 0 || k % 5 == 0 { return -1 } cur, res := 0 , 1 for { cur = (10 * cur + 1 ) % k if cur == 0 { return res } res++ } }
复杂度分析
练习题目推荐 29. 两数相除 166. 分数到小数 1071. 字符串的最大公因子
拓展思考 如果给定的 k 非常大,如何优化算法?