(
a
+
b
)
m
o
d
m
=
(
(
a
m
o
d
m
)
+
(
b
m
o
d
m
)
)
m
o
d
m
(
a
⋅
b
)
m
o
d
m
=
(
(
a
m
o
d
m
)
⋅
(
b
m
o
d
m
)
)
m
o
d
m
(a+b)\bmod m = ((a\bmod m) + (b\bmod m)) \bmod m \\ (a\cdot b) \bmod m=((a\bmod m)\cdot (b\bmod m)) \bmod m
(a+b)modm=((amodm)+(bmodm))modm(a⋅b)modm=((amodm)⋅(bmodm))modm
证明:根据带余除法,任意整数
a
a
a 都可以表示为
a
=
k
m
+
r
a=km+r
a=km+r,这里
r
r
r 相当于
a
m
o
d
m
a mod m
amodm。那么设
a
=
k
1
m
+
r
1
,
b
=
k
2
m
+
r
2
a=k_1m+r_1,\ b=k_2m+r_2
a=k1m+r1,b=k2m+r2。
第一个等式:
(
a
+
b
)
m
o
d
m
=
(
(
k
1
+
k
2
)
m
+
r
1
+
r
2
)
m
o
d
m
=
(
r
1
+
r
2
)
m
o
d
m
=
(
(
a
m
o
d
m
)
+
(
b
m
o
d
m
)
)
m
o
d
m
\begin{aligned} &(a+b) \bmod m\\ =&((k_1+k_2) m+r_1+r_2)\bmod m\\ =&(r_1+r_2)\bmod m\\ =&((a\bmod m) + (b\bmod m)) \bmod m \end{aligned}
===(a+b)modm((k1+k2)m+r1+r2)modm(r1+r2)modm((amodm)+(bmodm))modm
即:两个数相加对某个数求余等于两个数分别求余相加之后再求余。
第二个等式:
(
a
⋅
b
)
m
o
d
m
=
(
k
1
k
2
m
2
+
(
k
1
r
2
+
k
2
r
1
)
m
+
r
1
r
2
)
m
o
d
m
=
(
r
1
r
2
)
m
o
d
m
=
(
(
a
m
o
d
m
)
⋅
(
b
m
o
d
m
)
)
m
o
d
m
\begin{aligned} &(a\cdot b) \bmod m\\ =&(k_1k_2m^2+(k_1r_2+k_2r_1)m+r_1r_2)\bmod m\\ =&(r_1r_2)\bmod m\\ =&((a\bmod m)\cdot (b\bmod m)) \bmod m \end{aligned}
===(a⋅b)modm(k1k2m2+(k1r2+k2r1)m+r1r2)modm(r1r2)modm((amodm)⋅(bmodm))modm
classSolution:defsmallestRepunitDivByK(self, k:int)->int:
x =1% k # x 为余数
seen =set()# 创建一个无序集合,用于存储余数while x and x notin seen:# 当余数为0或者余数重复出现,退出循环
seen.add(x)
x =(10* x +1)% k
return-1if x elselen(seen)+1# 余数不为0,返回-1,余数为0,返回len(seen)+1
复杂度分析
时间复杂度:
O
(
k
)
\mathcal{O}(k)
O(k)。
空间复杂度:
O
(
k
)
\mathcal{O}(k)
O(k)。
方法二:抽屉原理
循环
k
k
k 次,如果没有算出0,则返回-1。为什么?模
k
k
k 的结果在
[
0
,
k
−
1
]
[0, k-1]
[0,k−1] 中,这有
k
k
k 个数字。如果循环
k
k
k 次还没有找到0,根据鸽巢原理(抽屉原理),必然有重复的数字。这也同时说明算法一的时间复杂度为
O
(
k
)
O(k)
O(k)。
classSolution:defsmallestRepunitDivByK(self, k:int)->int:if k %2==0or k %5==0:return-1
x =1% k
for i in count(1):# 一定有解if x ==0:return i
x =(x *10+1)% k
defcount(start=0, step=1):# count(10) --> 10 11 12 13 14 ...# count(2.5, 0.5) --> 2.5 3.0 3.5 ...
n = start
whileTrue:yield n
n += step
当对浮点数计数时,替换为乘法代码有时精度会更好,例如:(start + step * i for i in count())。
方法三:数学推导
设
n
n
n 的长度为
x
x
x,那么
n
=
1
0
x
−
1
9
n=\frac{10^x-1}{9}
n=910x−1。
n
n
n 是
k
k
k 的倍数,等价于
1
0
x
−
1
10^x-1
10x−1 是
9
k
9k
9k 的倍数,即
1
0
x
≡
1
(
m
o
d
9
k
)
10^x \equiv 1(mod 9k)
10x≡1(mod9k) 。
结论:最小的
x
x
x 必然是
ϕ
(
9
k
)
\phi(9k)
ϕ(9k) 的因子。
反证:如果
ϕ
(
9
k
)
=
p
x
+
r
(
0
<
r
<
x
)
\phi(9k) = px + r (0 < r <x)
ϕ(9k)=px+r(0<r<x),根据欧拉定理,
1
0
ϕ
(
9
k
)
=
(
10
)
P
﹒
10
”
=
10
”
=
1
(
m
o
d
9
k
)
10^{\phi(9k)}=(10)P﹒10”=10”= 1(mod 9k)
10ϕ(9k)=(10)P﹒10”=10”=1(mod9k),这说明有一个比
x
x
x 更小的
r
r
r,矛盾。
那么计算
ϕ
(
9
k
)
\phi(9k)
ϕ(9k) 并枚举其因子
d
d
d,用快速幂判断
1
0
d
m
o
d
(
9
k
)
10^d mod (9k)
10dmod(9k) 是否等于1。这一做法只需要
(
(
k
)
l
o
g
k
)
(\sqrt(k)log k )
((k)logk) 的时间。
一点优化
由于
n
n
n 的个位数是1,所以必然不是 2 的倍数和 5 的倍数。如果
k
k
k 是 2 的倍数或 5 的倍数,那么必然无解,返回 —1。否则一定有解。
证明:根据算法二,在计算过程中必然会出现两个数模
k
k
k 同余。设这两个数为
a
=
1
0
x
−
1
9
a=\frac{10^x-1}{9}
a=910x−1 和
b
=
1
0
y
−
1
9
b=\frac{10^y-1}{9}
b=910y−1,且
a
>
b
a > b
a>b。那么
a
−
b
a-b
a−b 是
k
k
k 的倍数。
注意
a
−
b
=
1
0
x
−
1
0
y
9
=
1
0
y
⋅
1
0
x
−
y
−
1
9
a-b=\frac{10^x-10^y}{9}=10^y\cdot\frac{10^{x-y}-1}{9}
a−b=910x−10y=10y⋅910x−y−1。
k
k
k在没有因子 2 和 5 的情况下,要想整除上式,必须要整除
1
0
x
−
y
−
1
9
\frac{10^{x-y}-1}{9}
910x−y−1,这说明
n
n
n 的长度可以是
x
−
y
x-y
x−y。
# 计算欧拉函数(n 以内的与 n 互质的数的个数)defphi(n:int)->int:
res = n
i =2while i * i <= n:if n % i ==0:
res = res // i *(i -1)while n % i ==0:
n //= i
i +=1if n >1:
res = res // n *(n -1)return res
classSolution:defsmallestRepunitDivByK(self, k:int)->int:if k %2==0or k %5==0:return-1
m = phi(k *9)# 从小到大枚举不超过 sqrt(m) 的因子
i =1while i * i <= m:if m % i ==0andpow(10, i, k *9)==1:return i
i +=1# 从小到大枚举不低于 sqrt(m) 的因子
i -=1whileTrue:if m % i ==0andpow(10, m // i, k *9)==1:return m // i
i -=1
复杂度分析
时间复杂度:
O
(
k
log
k
)
\mathcal{O}(\sqrt{k}\log k)
O(klogk)。计算
ϕ
(
9
k
)
\phi(9k)
ϕ(9k) 和枚举
ϕ
(
9
k
)
\phi(9k)
ϕ(9k) 的因子都需要
O
(
k
)
\mathcal{O}(\sqrt{k})
O(k) 的时间,对每个因子计算快速幂需要
O
(
log
k
)
\mathcal{O}(\log k)
O(logk) 的时间,所以时间复杂度为
O
(
k
log
k
)
\mathcal{O}(\sqrt{k}\log k)
O(klogk)。
举例,如果
time
[
i
]
=
1
\textit{time}[i]=1
time[i]=1,那么需要知道左边有多少个模 60 余数是 59 的数。
举例,如果
time
[
i
]
=
62
\textit{time}[i]=62
time[i]=62,那么需要知道左边有多少个模 60 余数是 58 的数。
一般地,对于
time
[
i
]
\textit{time}[i]
time[i],需要知道左边有多少个模 60 余数是
60
−
time
[
i
]
m
o
d
60
60-\textit{time}[i] mod 60
60−time[i]mod60 的数。 特别地,如果
time
[
i
]
\textit{time}[i]
time[i] 模 60 的余数是 0,那么需要知道左边有多少个模 60 余数也是 0 的数。 这两种情况可以合并为:累加左边
(
60
−
time
[
i
]
m
o
d
60
)
m
o
d
60
(60-\textit{time}[i] mod 60) mod 60
(60−time[i]mod60)mod60 的出现次数。
代码实现时,用一个长为 60 的数组
cnt
\textit{cnt}
cnt 维护
time
[
i
]
m
o
d
60
\textit{time}[i] mod 60
time[i]mod60 的出现次数。
classSolution:defnumPairsDivisibleBy60(self, time: List[int])->int:
ans =0
cnt =[0]*60for t in time:# 先查询 cnt,再更新 cnt,因为题目要求 i<j# 如果先更新,再查询,就把 i=j 的情况也考虑进去了
ans += cnt[(60- t %60)%60]
cnt[t %60]+=1return ans
复杂度分析
时间复杂度:
O
(
n
+
U
)
\mathcal{O}(n+U)
O(n+U),其中
n
n
n 为
nums
\textit{nums}
nums 的长度,
U
=
60
U=60
U=60。