A clear explanation of finding the smallest repunit divisible by K by tracking remainders to detect cycles.
Problem Restatement
We need to find the smallest positive integer N consisting only of the digit 1 (a repunit) such that N is divisible by k.
In other words, find the smallest n such that:
N = 111...1 (n ones)
N % k == 0Return n, or -1 if no such positive integer exists.
The official constraints state that 1 <= k <= 10^5.
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer k |
| Output | Length of the smallest repunit divisible by k, or -1 |
Function shape:
def smallestRepunitDivByK(k: int) -> int:
...Examples
Example 1:
k = 1N = 1, which is divisible by 1.
Answer:
1Example 2:
k = 2No repunit is divisible by 2. All repunits are odd numbers.
Answer:
-1Example 3:
k = 3N = 111 = 3 * 37, divisible by 3.
Answer:
3Key Insight
If k is divisible by 2 or 5, no repunit can be divisible by k, because repunits always end in 1 (not an even number or multiple of 5).
Otherwise, we compute the remainder of N_n = 111...1 (n ones) modulo k.
The recurrence is:
N_n = N_{n-1} * 10 + 1
remainder_n = (remainder_{n-1} * 10 + 1) % kBy the pigeonhole principle, if we never hit remainder 0, we will eventually see a repeated remainder after at most k steps. A repeated remainder means we are in a cycle and will never reach 0.
Algorithm
- If
k % 2 == 0ork % 5 == 0, return-1. - Start with
remainder = 0. - For
nfrom1tok:remainder = (remainder * 10 + 1) % k.- If
remainder == 0, returnn.
- Return
-1.
Correctness
By pigeonhole, there are only k possible remainders modulo k. If after k steps we have not found remainder 0, then some remainder has appeared twice. This means the sequence of remainders is periodic and will never reach 0.
Edge Cases
- Duplicates usually matter; store counts when a set would lose necessary multiplicity.
- Update the frequency structure in the same order the invariant assumes.
- Check empty or one-element inputs if the problem allows them.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(k) | At most k iterations |
| Space | O(1) | Constant space |
Common Pitfalls
- Do not optimize away the invariant; the code should still make it clear what condition is being maintained.
- Prefer problem-specific names over one-letter variables once the logic becomes stateful.
Implementation
class Solution:
def smallestRepunitDivByK(self, k: int) -> int:
if k % 2 == 0 or k % 5 == 0:
return -1
remainder = 0
for n in range(1, k + 1):
remainder = (remainder * 10 + 1) % k
if remainder == 0:
return n
return -1Code Explanation
Early exit for even or multiple-of-5 values:
if k % 2 == 0 or k % 5 == 0:
return -1Compute remainders iteratively:
remainder = (remainder * 10 + 1) % kThis avoids dealing with very large numbers.
Return the length when the remainder first reaches 0:
if remainder == 0:
return nTesting
def run_tests():
s = Solution()
assert s.smallestRepunitDivByK(1) == 1
assert s.smallestRepunitDivByK(2) == -1
assert s.smallestRepunitDivByK(3) == 3
assert s.smallestRepunitDivByK(7) == 6
assert s.smallestRepunitDivByK(5) == -1
print("all tests passed")
run_tests()| Test | Expected | Why |
|---|---|---|
1 | 1 | 1 % 1 == 0 |
2 | -1 | Repunits are odd |
3 | 3 | 111 = 3 × 37 |
7 | 6 | 111111 = 7 × 15873 |
5 | -1 | Repunits don’t end in 0 or 5 |