二分查找
数学
题目描述
以字符串的形式给出 n
, 以字符串的形式返回 n
的最小 好进制 。
如果 n
的 k(k>=2)
进制数的所有数位全为1,则称 k(k>=2)
是 n
的一个 好进制 。
示例 1:
输入: n = "13"
输出: "3"
解释: 13 的 3 进制是 111。
示例 2:
输入: n = "4681"
输出: "8"
解释: 4681 的 8 进制是 11111。
示例 3:
输入: n = "1000000000000000000"
输出: "999999999999999999"
解释: 1000000000000000000 的 999999999999999999 进制是 11。
提示:
n
的取值范围是 [3, 1018 ]
n
没有前导 0
解法
方法一:数学
假设 \(n\) 在 \(k\) 进制下的所有位数均为 \(1\) ,且位数为 \(m+1\) ,那么有式子 ①:
\[
n=k^0+k^1+k^2+...+k^m
\]
当 \(m=0\) 时,上式 \(n=1\) ,而题目 \(n\) 取值范围为 \([3, 10^{18}]\) ,因此 \(m>0\) 。
当 \(m=1\) 时,上式 \(n=k^0+k^1=1+k\) ,即 \(k=n-1>=2\) 。
我们来证明一般情况下的两个结论,以帮助解决本题。
结论一: \(m<\log _{k} n\)
注意到式子 ① 是个首项为 \(1\) ,且公比为 \(k\) 的等比数列。利用等比数列求和公式,我们可以得出:
\[
n=\frac{1-k^{m+1}}{1-k}
\]
变形得:
\[
k^{m+1}=k \times n-n+1 < k \times n
\]
移项得:
\[
m<\log _{k} n
\]
题目 \(n\) 取值范围为 \([3, 10^{18}]\) ,又因为 \(k>=2\) ,因此 \(m<\log _{k} n<\log _{2} 10^{18}<60\) 。
结论二: $k=\left \lfloor \sqrt[m]{n} \right \rfloor $
\[
n=k^0+k^1+k^2+...+k^m>k^m
\]
根据二项式定理:
\[
(a+b)^{n}=\sum_{k=0}^{n}\left(\begin{array}{l}
n \\
k
\end{array}\right) a^{n-k} b^{k}
\]
整合,可得:
\[
(k+1)^{m}=\left(\begin{array}{c}
m \\
0
\end{array}\right) k^{0}+\left(\begin{array}{c}
m \\
1
\end{array}\right) k^{1}+\left(\begin{array}{c}
m \\
2
\end{array}\right) k^{2}+\cdots+\left(\begin{array}{c}
m \\
m
\end{array}\right) k^{m}
\]
当 \(m>1\) 时,满足:
\[
\forall i \in[1, m-1],\left(\begin{array}{c}
m \\
i
\end{array}\right)>1
\]
所以有:
\[
\begin{aligned}
(k+1)^{m} &=\left(\begin{array}{c}
m \\
0
\end{array}\right) k^{0}+\left(\begin{array}{c}
m \\
1
\end{array}\right) k^{1}+\left(\begin{array}{c}
m \\
2
\end{array}\right) k^{2}+\cdots+\left(\begin{array}{c}
m \\
m
\end{array}\right) k^{m} \\
&>k^{0}+k^{1}+k^{2}+\cdots+k^{m}=n
\end{aligned}
\]
即:
\[
k < \sqrt[m]{n} < k+1
\]
由于 \(k\) 是整数,因此 $k=\left \lfloor \sqrt[m]{n} \right \rfloor $。
综上,依据结论一,我们知道 \(m\) 的取值范围为 \([1,log_{k}n)\) ,且 \(m=1\) 时必然有解。随着 \(m\) 的增大,进制 \(k\) 不断减小。所以我们只需要从大到小检查每一个 \(m\) 可能的取值,利用结论二快速算出对应的 \(k\) 值,然后校验计算出的 \(k\) 值是否有效即可。如果 \(k\) 值有效,我们即可返回结果。
时间复杂度 \(O(log^{2}n)\) 。
Python3 Java C++
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21 class Solution :
def smallestGoodBase ( self , n : str ) -> str :
def cal ( k , m ):
p = s = 1
for i in range ( m ):
p *= k
s += p
return s
num = int ( n )
for m in range ( 63 , 1 , - 1 ):
l , r = 2 , num - 1
while l < r :
mid = ( l + r ) >> 1
if cal ( mid , m ) >= num :
r = mid
else :
l = mid + 1
if cal ( l , m ) == num :
return str ( l )
return str ( num - 1 )
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 class Solution {
public String smallestGoodBase ( String n ) {
long num = Long . parseLong ( n );
for ( int len = 63 ; len >= 2 ; -- len ) {
long radix = getRadix ( len , num );
if ( radix != - 1 ) {
return String . valueOf ( radix );
}
}
return String . valueOf ( num - 1 );
}
private long getRadix ( int len , long num ) {
long l = 2 , r = num - 1 ;
while ( l < r ) {
long mid = l + r >>> 1 ;
if ( calc ( mid , len ) >= num )
r = mid ;
else
l = mid + 1 ;
}
return calc ( r , len ) == num ? r : - 1 ;
}
private long calc ( long radix , int len ) {
long p = 1 ;
long sum = 0 ;
for ( int i = 0 ; i < len ; ++ i ) {
if ( Long . MAX_VALUE - sum < p ) {
return Long . MAX_VALUE ;
}
sum += p ;
if ( Long . MAX_VALUE / p < radix ) {
p = Long . MAX_VALUE ;
} else {
p *= radix ;
}
}
return sum ;
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19 class Solution {
public :
string smallestGoodBase ( string n ) {
long v = stol ( n );
int mx = floor ( log ( v ) / log ( 2 ));
for ( int m = mx ; m > 1 ; -- m ) {
int k = pow ( v , 1.0 / m );
long mul = 1 , s = 1 ;
for ( int i = 0 ; i < m ; ++ i ) {
mul *= k ;
s += mul ;
}
if ( s == v ) {
return to_string ( k );
}
}
return to_string ( v - 1 );
}
};
GitHub