文章目录2021/6/18
一、LeetCode每日一题
题目链接
题目大意
对于给定的整数
n
n
n, 如果
n
n
n的
k
(
k
≥
2
)
k (k\geq2)
k(k≥2)进制数的所有数位全为
1
1
1,则称
k
k
k是
n
n
n的一个好进制。请求出给定数字的最小好进制。
- 3 3 3的好进制为 2 2 2结果是 ( 11 ) (11) (11)
- 13 13 13的好进制为 3 ( 111 ) 3(111) 3(111)和 12 ( 11 ) 12(11) 12(11),所以 13 13 13的最小好进制为 3 3 3
题解
观察数据范围 3 ≤ n ≤ 1 0 18 3 \leq n \leq 10^{18} 3≤n≤1018,提示我们可以设计最坏 O ( l o g 4 n ) O(log^4n) O(log4n)的算法。
- 首先这道题正着思考很不好入手,因为给你的数字你也不知道它的最小好进制有什么性质,而且还不具备单调性,也就是不可二分查找。例如, 3 3 3是 13 13 13的好进制,但是 4 、 5 、 6 4、5、6 4、5、6就不是 13 13 13好进制。
- 正难则反,思考 n n n的好进制下的数字一定是怎么样的呢?
- 一定是 1111...11 1111...11 1111...11这样的形式对吧,那么我们想到能不能枚举这个数字的长度(记为 i i i),然后去检查呢?
- 答案是可以的,而且还可以贪心的利用
i
i
i的性质,如果
i
i
i越大,代表这个数字越长,那么对应的好进制就应该更小。所以我们现在的任务就是确定这个长度的边界。
- 如果你要把这个 1111...11 1111...11 1111...11转化为十进制要怎么做呢?
- 首先假设这个数字是 n n n在 k ( k ≥ ) k(k\geq) k(k≥)进制转化后的结果。
- 那么就有
1
+
1
∗
k
+
1
∗
k
2
+
.
.
+
1
∗
k
i
−
1
=
n
1+1*k +1*k^2+..+1*k^{i-1}=n
1+1∗k+1∗k2+..+1∗ki−1=n
- 例如: 111 111 111在 3 3 3进制下转化为 1 + 1 ∗ 3 + 1 ∗ 3 2 = 13 1+1*3+1*3^2=13 1+1∗3+1∗32=13
- 发现这是一个等比数列求和,套用求和公式得到 1 − k i − 1 1 − k = k i − 1 − 1 k − 1 = n \frac{1-k^{i-1}}{1-k}=\frac{k^{i-1}-1}{k-1}=n 1−k1−ki−1=k−1ki−1−1=n
- 移项得到 k i − 1 = n ∗ ( k − 1 ) + 1 ≤ n ∗ k ⟹ k i − 2 ≤ n ⟹ i − 2 ≤ log k n k^{i-1}=n*(k-1)+1\leq n*k\Longrightarrow k^{i-2}\leq n \Longrightarrow i-2\leq\log_kn ki−1=n∗(k−1)+1≤n∗k⟹ki−2≤n⟹i−2≤logkn
- 当 k k k取 2 , n 2,n 2,n取 1 0 18 10^{18} 1018时 i i i获得最大值 64 64 64,加上 k ≥ 2 k\geq 2 k≥2就确定了 2 ≤ i ≤ 64 2\leq i \leq 64 2≤i≤64
- 得到解法1
- 倒序枚举 i i i,然后利用二分查找去检查每个 k k k,因为 k k k现在是具有单调性的(对于所有的 k k k当 k i − 1 − 1 k − 1 ≥ n \frac{k^{i-1}-1}{k-1}\geq n k−1ki−1−1≥n时 k + 1 k+1 k+1也同时成立)。
- 最后只用观察是否有满足条件的 k k k即可,有则返回,否则继续递减 i i i.
public String smallestGoodBase(String n) {
// 枚举111111的长度
BigInteger res = new BigInteger(n);
for (int i = 64; i >= 2; i--) {
BigInteger k = lowerBound(res, i);
if (k.equals(BigInteger.ONE)) continue;
System.out.println(k.toString());
}
return "";
}
private BigInteger lowerBound(BigInteger res, int i) {
int c = check(res, BigInteger.TWO, i);
if (c > 0) return BigInteger.ONE;
else if (c == 0) return BigInteger.TWO;
BigInteger l = BigInteger.TWO, r = res;
while (l.add(BigInteger.ONE).compareTo(r) < 0) {
BigInteger mid = l.add(r).divide(BigInteger.TWO);
if (check(res, mid, i) >= 0) r = mid;
else l = mid;
}
return check(res, r, i) == 0 ? r : BigInteger.ONE;
}
private int check(BigInteger res, BigInteger k, int i) {
BigInteger top = k.pow(i).subtract(BigInteger.ONE);
BigInteger down = k.subtract(BigInteger.ONE);
return top.divide(down).compareTo(res);
}
- 由于上述写法过于复杂所以我们可以继续探索有什么我们没有利用到的性质。
- 观察 n = 1 + k 1 + k 2 + . . + k i − 1 > k i − 1 ⟹ k < n i − 1 n=1+k^1+k^2+..+k^{i-1}>k^{i-1}\Longrightarrow k<\sqrt[i-1]{n} n=1+k1+k2+..+ki−1>ki−1⟹k<i−1n
- 由二项式定理得 ( k + 1 ) i − 1 = C i − 1 0 ∗ 1 + C i − 1 1 ∗ k 1 + . . . + C i − 1 i − 1 ∗ k i − 1 > n (k+1)^{i-1}=C_{i-1}^{0}*1+C_{i-1}^{1}*k^1+...+C_{i-1}^{i-1}*k^{i-1}>n (k+1)i−1=Ci−10∗1+Ci−11∗k1+...+Ci−1i−1∗ki−1>n
- 所以 k < n i − 1 < k + 1 k<\sqrt[i-1]{n}<k+1 k<i−1n<k+1,又因为 k k k进制一定是整数,所以 k = ⌊ n i − 1 ⌋ k=\lfloor\sqrt[i-1]{n}\rfloor k=⌊i−1n⌋
- 得到解法2
- 还是倒序枚举 i i i,对于每一个 i i i求得对应的 k k k。
- 检查 k k k是否可行,是就返回,否则继续枚举 i i i
- 检查是指对于 k i − 1 − 1 k − 1 \frac{k^{i-1}-1}{k-1} k−1ki−1−1判定是否等于 n n n
public String smallestGoodBase(String n) {
long nval = Long.parseLong(n);
for (int i = 64; i >= 2; i--) {
long k = (long)Math.pow(nval, 1./(i - 1));
if (good(nval, k, i)) return k + "";
}
return nval - 1 + "";
}
private boolean good(long n, long k, int i) {
if (k <= 1) return false;
BigInteger kk = BigInteger.valueOf(k);
BigInteger nn = BigInteger.valueOf(n);
// (k^i - 1) / (k - 1) =?= n
return kk.pow(i).subtract(BigInteger.ONE).
divide((kk.subtract(BigInteger.ONE))).
equals(nn);
}
时空复杂度分析
- 解法一
- 由于大整数运算的时间复杂度可看作整数长度也就是 O ( log n ) O(\log n) O(logn),所以总的时间复杂度为 O ( log 2 n ) O(\log^2 n) O(log2n)
- 大整数存储贡献整个空间复杂度 O ( log n ) O(\log n) O(logn)
- 解法二
- 时间复杂度由 p o w ( ) pow() pow()函数和大整数操作贡献,所以总的时间复杂度为 O ( log n ) O(\log n) O(logn)
- 大整数存储贡献整个空间复杂度 O ( log n ) O(\log n) O(logn)