## 题目地址（483. 最小好进制）

https://leetcode-cn.com/problems/smallest-good-base/

## 题目描述

```
对于给定的整数 n, 如果n的k（k>=2）进制数的所有数位全为1，则称 k（k>=2）是 n 的一个好进制。

以字符串的形式给出 n, 以字符串的形式返回 n 的最小好进制。

 

示例 1：

输入："13"
输出："3"
解释：13 的 3 进制是 111。
示例 2：

输入："4681"
输出："8"
解释：4681 的 8 进制是 11111。
示例 3：

输入："1000000000000000000"
输出："999999999999999999"
解释：1000000000000000000 的 999999999999999999 进制是 11。
 

提示：

n的取值范围是 [3, 10^18]。
输入总是有效且没有前导 0。

```

## 前置知识

- 二分法
- 进制转换

## 公司

- 暂无

## 思路

题目虽然很短，题意也不难理解。但是要想做出来还是要费一番功夫的。

题目的意思是给你一个数字 n，让你返回一个进制 k，使得 n 的 k 进制表示为 1111...111，即一个全 1 的表示，并且**需要返回满足条件最小的 k**。

朴素的思路是一个个尝试。不过就算想要暴力求解也要一点**进制转换**的知识。这个知识点是： 一个数字 n 的 k 进制表示可以按照 $a * k^0 + b * k^1 + c * k^2 + ... + m * k^{N-1}$ 的方式转化为十进制，其中 N 为 数字 n 的 k 进制位数。 比如十进制 3 的二进制是为 11，其位数就是 2。再比如十进制的 199，位数为 3。由于我们要求的是位全为 1 的数，因此系数全部为 1，也就是 a,b,c ...., m 全部为 1。

因此我们可从 k = 2 开始枚举，直到 n - 1，线性尝试是否可满足条件。

> 一进制只能有 0， 不可能有 1，故不考虑。 由于 n 进行最多 n 个数，上限是 n - 1，因此我们的枚举上限也是 n - 1。

核心伪代码：

```go
n = int(n)
// 上面提到的 base 进制转十进制公式
func sum_with(base, N):
    return sum(1 * base ** i for i in range(N))

for k=2 to n - 1:
    if sum_with(k, N) == n: return k
```

可问题是 N 如何求出呢？

朴素的思路仍然是线性枚举。但是我们的搜索区间如何确定呢？我们知道对于一个数 n 来说，其 2 进制表示的长度一定是大于 3 进制表示的长度的。更一般而言，如果 k1 > k2，那么对于一个数字 n 的 k1 进制表示的位数一定小于 k2 进行表示的位数。 因此我们的解空间就是 [1,x] 其中 x 为 n 的二进制表示的位数。也就是说，我们可逐一枚举 N 的值 N`。

注意到需要返回的是最小的 k 进制，结合前面说的进制越小 N 越大的知识，我们应该使用从后往前倒着遍历，这样遇到满足条件的 k 可直接返回，因此在平均意义上时间复杂度更低。

```go
n = int(n)
// 上面提到的 base 进制转十进制公式
func sum_with(base, N):
    return sum(1 * base ** i for i in range(N))
for N=x to 1:
    for k=2 to n - 1:
        if sum_with(k, N) == n: return k
```

注意这里的 x 到 1 的枚举没有必要线性枚举，而是可使用二分搜索的方式进行，其依据是**如果进制 k 的 N 位表示大于 n，那么 N\`表示就不用看了，肯定都大，其中 N\`是大于 N 的整数**。

让我们来计算下上面算法的时间复杂度。外层二分枚举的时间复杂度 $O(loglogn)$，内层枚举的时间复杂度是 n，sum_with 的时间复杂度为 logn，因此总的时间复杂度为 $n\times loglogn\times logn$。

到这里为止，算法勉强可以通过了。不过仍然有优化空间。注意到 sum_with 部分其实就是一个等比数列求和，因此我们可以使用等比数列求和公式，从而将 sum_with 的时间复杂度降低到 $O(1)$。

即使如此算法的时间复杂度也是 $O(n\times loglogn)$，代入到题目是的数据范围 $[3, 10^{18}]$，也是在超时边缘。使用数学法降维打击可以获得更好的效率，感兴趣的可以研究一下。

## 关键点解析

- 利用等比数列求和公式可降低时间复杂度
- 从进制转换入手发现单调性，从而使用二分解决

## 代码

代码支持：Python3

Python3 Code:

```python
class Solution:
    def smallestGoodBase(self, n: str) -> str:
        n = int(n)
        # 上面提到的 base 进制转十进制公式。
        # 使用等比数列求和公式可简化时间复杂度
        def sum_with(base, N):
            return (1 - base ** N) // (1 - base)
            # return sum(1 * base ** i for i in range(N))
        # bin(n) 会计算出 n 的二进制表示， 其会返回形如 '0b10111' 的字符串，因此需要减去 2。
        for N in range(len(bin(n)) - 2, 0, -1):
            l = 2
            r = n - 1
            while l <= r:
                mid = (l + r) // 2
                v = sum_with(mid, N)

                if v < n:
                    l = mid + 1
                elif v > n:
                    r = mid - 1
                else:
                    return str(mid)

```

**复杂度分析**

- 时间复杂度：$O(n\times loglogn)$
- 空间复杂度：$O(1)$
