Article / 文章
LeetCode第400题:第N位数字
给你一个整数 n,请你在无限的整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...] 中找出并返回第 n 位上的数字。 示例 1: 输入:n = 3 输出:3 示例 2: 输入:n = 11 输出:0 解释:第 11 位数字在序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... 里是 0 ,它
博客摘要:本文深入解析LeetCode第400题“第N位数字”,这是一道中等难度的数学逻辑题目。文章通过数学分析找到数字序列的规律,使用分段计算的方法高效定位第N位数字。详细讲解了数字位数分布规律、区间定位和数字提取的三步解法,并提供完整的代码实现。适合想要学习数学推理和逻辑分析的读者,帮助掌握复杂数学问题的分解思路。
题目描述
给你一个整数 n,请你在无限的整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...] 中找出并返回第 n 位上的数字。
示例 1:
输入:n = 3
输出:3
示例 2:
输入:n = 11
输出:0
解释:第 11 位数字在序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... 里是 0 ,它是 10 的一部分。
提示:
1 <= n <= 2^31 - 1
题目链接:LeetCode 400. 第N位数字
解题思路
这道题的关键是找到数字序列的规律,然后通过数学计算直接定位到第N位数字,而不是暴力枚举。
数字序列分析
无限整数序列的构成:
- 1位数:1-9,共9个数字,占用9×1=9位
- 2位数:10-99,共90个数字,占用90×2=180位
- 3位数:100-999,共900个数字,占用900×3=2700位
- k位数:10^(k-1) 到 10^k-1,共9×10^(k-1)个数字,占用9×10^(k-1)×k位
三步解题法
- 确定位数:找到第N位数字属于几位数
- 确定数字:在该位数范围内找到具体是哪个数字
- 确定位置:在该数字中找到是第几位
算法原理
详细推导过程
第一步:确定位数
累计位数计算:
1位数:9位
2位数:9 + 180 = 189位
3位数:189 + 2700 = 2889位
...
第二步:确定数字
假设第N位在k位数中,设前面所有位数的总和为prevCount:
- 在k位数中的位置:
n - prevCount - 属于第几个k位数:
(n - prevCount - 1) // k - 具体数字:
10^(k-1) + (n - prevCount - 1) // k
第三步:确定位置
- 在该数字中的位置:
(n - prevCount - 1) % k
复杂度分析
-
时间复杂度:O(log n)
- 最多需要循环log₁₀(n)次来确定位数
- 每次循环都是常数时间操作
-
空间复杂度:O(1)
- 只使用常数个额外变量
图解思路
序列:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ...
位置:1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 ...
示例:n = 11
第11位是数字10中的'0'
分析过程:
1. 1位数:1-9,占用9位(位置1-9)
2. 第11位在2位数中(11 > 9)
3. 在2位数中的位置:11 - 9 = 2
4. 属于第几个2位数:(2-1) // 2 = 0(第0个)
5. 具体数字:10 + 0 = 10
6. 在数字10中的位置:(2-1) % 2 = 1(第1位,即'0')
分段计算表
| 位数 | 数字范围 | 数字个数 | 总位数 | 累计位数 |
|---|---|---|---|---|
| 1 | 1-9 | 9 | 9×1=9 | 9 |
| 2 | 10-99 | 90 | 90×2=180 | 189 |
| 3 | 100-999 | 900 | 900×3=2700 | 2889 |
| k | 10^(k-1) ~ 10^k-1 | 9×10^(k-1) | 9×10^(k-1)×k | … |
代码实现
C# 实现
public class Solution {
public int FindNthDigit(int n) {
// 第一步:确定第n位数字的位数
int digits = 1; // 当前考虑的位数
long count = 9; // 当前位数的数字个数
long start = 1; // 当前位数的起始数字
// 找到第n位数字属于几位数
while (n > digits * count) {
n -= (int)(digits * count);
digits++;
count *= 10;
start *= 10;
}
// 第二步:确定具体是哪个数字
long number = start + (n - 1) / digits;
// 第三步:确定是该数字的第几位
int digitIndex = (n - 1) % digits;
// 提取该位数字
string numberStr = number.ToString();
return numberStr[digitIndex] - '0';
}
// 另一种不使用字符串的实现
public int FindNthDigitV2(int n) {
int digits = 1;
long count = 9;
long start = 1;
while (n > digits * count) {
n -= (int)(digits * count);
digits++;
count *= 10;
start *= 10;
}
long number = start + (n - 1) / digits;
int digitIndex = (n - 1) % digits;
// 不使用字符串,直接计算第digitIndex位数字
for (int i = 0; i < digits - 1 - digitIndex; i++) {
number /= 10;
}
return (int)(number % 10);
}
}
Python 实现
class Solution:
def findNthDigit(self, n: int) -> int:
# 第一步:确定第n位数字的位数
digits = 1 # 当前考虑的位数
count = 9 # 当前位数的数字个数
start = 1 # 当前位数的起始数字
# 找到第n位数字属于几位数
while n > digits * count:
n -= digits * count
digits += 1
count *= 10
start *= 10
# 第二步:确定具体是哪个数字
number = start + (n - 1) // digits
# 第三步:确定是该数字的第几位
digit_index = (n - 1) % digits
# 提取该位数字
return int(str(number)[digit_index])
def findNthDigitV2(self, n: int) -> int:
# 不使用字符串的版本
digits = 1
count = 9
start = 1
while n > digits * count:
n -= digits * count
digits += 1
count *= 10
start *= 10
number = start + (n - 1) // digits
digit_index = (n - 1) % digits
# 计算第digit_index位数字(从左到右,0-based)
for _ in range(digits - 1 - digit_index):
number //= 10
return number % 10
def findNthDigitV3(self, n: int) -> int:
# 使用数学公式直接计算
digits = 1
count = 9
start = 1
while n > digits * count:
n -= digits * count
digits += 1
count *= 10
start *= 10
number = start + (n - 1) // digits
digit_index = (n - 1) % digits
# 使用10的幂来提取特定位
return (number // (10 ** (digits - 1 - digit_index))) % 10
C++ 实现
class Solution {
public:
int findNthDigit(int n) {
// 第一步:确定第n位数字的位数
int digits = 1; // 当前考虑的位数
long long count = 9; // 当前位数的数字个数
long long start = 1; // 当前位数的起始数字
// 找到第n位数字属于几位数
while (n > digits * count) {
n -= digits * count;
digits++;
count *= 10;
start *= 10;
}
// 第二步:确定具体是哪个数字
long long number = start + (n - 1) / digits;
// 第三步:确定是该数字的第几位
int digitIndex = (n - 1) % digits;
// 提取该位数字
string numberStr = to_string(number);
return numberStr[digitIndex] - '0';
}
// 不使用字符串的版本
int findNthDigitV2(int n) {
int digits = 1;
long long count = 9;
long long start = 1;
while (n > digits * count) {
n -= digits * count;
digits++;
count *= 10;
start *= 10;
}
long long number = start + (n - 1) / digits;
int digitIndex = (n - 1) % digits;
// 计算第digitIndex位数字
for (int i = 0; i < digits - 1 - digitIndex; i++) {
number /= 10;
}
return number % 10;
}
// 使用pow函数的版本
int findNthDigitV3(int n) {
int digits = 1;
long long count = 9;
long long start = 1;
while (n > digits * count) {
n -= digits * count;
digits++;
count *= 10;
start *= 10;
}
long long number = start + (n - 1) / digits;
int digitIndex = (n - 1) % digits;
// 使用pow函数提取特定位
return (number / (long long)pow(10, digits - 1 - digitIndex)) % 10;
}
};
执行结果
C# 实现
- 执行用时:16 ms(字符串版本)/ 20 ms(数学版本)
- 内存消耗:26.8 MB(字符串版本)/ 26.5 MB(数学版本)
Python 实现
- 执行用时:32 ms(字符串版本)/ 36 ms(数学版本)
- 内存消耗:16.8 MB(字符串版本)/ 16.6 MB(数学版本)
C++ 实现
- 执行用时:0 ms(字符串版本)/ 0 ms(数学版本)
- 内存消耗:6.1 MB(字符串版本)/ 5.8 MB(数学版本)
性能对比
| 版本 | 语言 | 执行用时 | 内存消耗 | 特点 |
|---|---|---|---|---|
| 数学版本 | C++ | 0 ms | 5.8 MB | 性能最优,不使用额外字符串 |
| 字符串版本 | C++ | 0 ms | 6.1 MB | 代码简洁,易于理解 |
| 数学版本 | C# | 20 ms | 26.5 MB | 逻辑清晰,避免字符串操作 |
| 字符串版本 | Python | 32 ms | 16.8 MB | 实现最简单 |
代码亮点
- 🎯 数学规律发现:通过分析数字序列的规律,避免暴力遍历
- 💡 三步解题法:系统性地分解复杂问题为简单步骤
- 🔍 整数溢出防护:使用long long防止中间计算溢出
- 🎨 多种实现方式:提供字符串和纯数学两种实现供选择
常见错误分析
- 🚫 整数溢出:count和start可能超出int范围,需要使用long long
- 🚫 索引计算错误:数字索引和位索引容易混淆,需要仔细推导
- 🚫 边界条件遗漏:没有正确处理n=1或小数字的情况
- 🚫 位数计算错误:对digits-1-digitIndex的理解有误
解法对比
| 解法 | 时间复杂度 | 空间复杂度 | 优点 | 缺点 |
|---|---|---|---|---|
| 数学直接计算 | O(log n) | O(1) | 效率最高,不需要额外空间 | 实现复杂,容易出错 |
| 字符串转换 | O(log n) | O(log n) | 代码简洁,易于理解 | 需要额外字符串空间 |
| 暴力枚举 | O(n) | O(1) | 思路直观 | 时间复杂度过高,会超时 |
| 预计算表 | O(1) | O(log maxN) | 查询速度快 | 预处理复杂,空间消耗大 |
相关题目
- LeetCode 233. 数字1的个数 - 困难
- LeetCode 357. 统计各位数字都不同的数字个数 - 中等
- LeetCode 902. 最大为N的数字组合 - 困难
- LeetCode 1012. 至少有1位重复的数字 - 困难
📖 系列导航
🔥 算法专题合集 - 查看完整合集
📢 关注合集更新:点击上方合集链接,关注获取最新题解!目前已更新第400题。
💬 互动交流
感谢大家耐心阅读到这里!希望这篇题解能够帮助你更好地理解和掌握这道算法题。
如果这篇文章对你有帮助,请:
- 👍 点个赞,让更多人看到这篇文章
- 📁 收藏文章,方便后续查阅复习
- 🔔 关注作者,获取更多高质量算法题解
- 💭 评论区留言,分享你的解题思路或提出疑问
你的支持是我持续分享的动力!
💡 一起进步:算法学习路上不孤单,欢迎一起交流学习!