forked from azl397985856/leetcode
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
lucifer
committed
Jan 19, 2021
1 parent
8566ece
commit 6a633fa
Showing
5 changed files
with
144 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,135 @@ | ||
## 题目地址(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 的数,因此系数全部为 0,也就是 a,b,c ...., m 全部为 0。 | ||
|
||
因此我们可从 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 越大的知识,我们应该使用从后往前遍历,这样在平均意义上,时间复杂度更低。 | ||
|
||
```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)$ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters