LeetCode 400 - 第 N 位数字
文章目录
- 摘要
- 描述
- 题解答案
- 题解代码分析
- 示例测试及结果
- 时间复杂度
- 空间复杂度
- 总结
摘要
这道题看起来像是“在无限序列里随便数第 n 个字符”,但要做到O(1) 空间、O(d) 时间(d = 位数长度,通常 ≤ 10)并不是直接依次拼接数字就能高效做的。我们需要利用“按位段(1位、2位、3位……)”来跳过大量数字,定位到目标数字,然后从数字中取出对应那一位。本文用 Swift 给出清晰的实现、细致代码解析与示例测试,便于在工程中直接复用。
描述
考虑无限整数序列:
"1234567891011121314..."
给定整数 n
(1 ≤ n ≤ 2³¹-1),返回该序列第 n
位上的数字(单个十进制数字 0–9)。
举例:
n = 3
→ 第 3 位是3
n = 11
→ 序列前 11 个字符是12345678910
,第 11 位是0
(来自数字 10)
题解答案
核心思路分三步:
- 按位数分段跳过:先看 1 位数(1…9)共 9 个数,占 9×1 = 9 位;再看 2 位数(10…99)共 90 个数,占 90×2 位,依次类推,直到
n
落在当前位段中。 - 确定具体数字:假定
n
最终落在digit
位数段,段起始数字为start = 10^(digit-1)
,则第(n-1)/digit
个数字(从 0 开始)即为目标数num = start + (n-1)/digit
。 - 在
num
中取第(n-1) % digit
个数字(从左到右),返回它的数值。
实现要点:用 Int64
做中间计算以免乘法溢出(n 最大可达 2³¹-1),取位时可用字符串或数学方式。下面给出可运行的 Swift 实现。
题解代码分析
-
主要变量:
n64
:将输入n
转为Int64
,方便大数运算。digit
:当前位数(从 1 开始)。count
:当前位数段的数字个数,例如当digit = 2
时count = 90
(10…99)。start
:当前段起始数字,如1, 10, 100, ...
。
-
循环:当
n64 > count * digit
时说明目标不在当前段,减去这段的总位数后进下一段。 -
最终
num
用start + (n64 - 1) / digit
计算。再从num
中提取对应的位(使用字符串更直观,也足够快,位数 ≤ 10)。
下面是完整代码(包含注释与示例测试)。
import Foundationclass Solution {func findNthDigit(_ n: Int) -> Int {var n64 = Int64(n)var digit: Int64 = 1 // 当前数字位数(1 表示 1 位数)var count: Int64 = 9 // 当前位数的数字个数(1 位数有 9 个,2 位数有 90 个...)var start: Int64 = 1 // 当前位数段起始数字(1, 10, 100, ...)// 找到 n 落在哪个位数段while n64 > digit * count {n64 -= digit * countdigit += 1count *= 10start *= 10}// 在当前位段中,第 (n64-1)/digit 个数(从 0 开始)let offset = (n64 - 1) / digitlet num = start + offset // 目标数字let indexInNum = Int((n64 - 1) % digit) // 在目标数字内的索引(从左到右,0-based)// 将 num 转为字符串,从中取出对应字符并返回其数值let s = String(num)let char = s[s.index(s.startIndex, offsetBy: indexInNum)]return Int(String(char))!}
}// 示例测试
let sol = Solution()
print(sol.findNthDigit(3)) // 输出: 3
print(sol.findNthDigit(11)) // 输出: 0
print(sol.findNthDigit(1)) // 输出: 1
print(sol.findNthDigit(9)) // 输出: 9
print(sol.findNthDigit(10)) // 输出: 1 (第 10 位是数字 '1',来自 10)
print(sol.findNthDigit(190)) // 测试边界附近:190 对应 100 的第一个数字 '1'
示例测试及结果
上面示例结果:
findNthDigit(3) -> 3
findNthDigit(11) -> 0
findNthDigit(1) -> 1
findNthDigit(9) -> 9
findNthDigit(10) -> 1
findNthDigit(190) -> (根据运行结果)
你可以把上面的代码复制到 Xcode Playground 或 Swift REPL 里运行,会得到预期输出。
时间复杂度
- 找到位数段时,
digit
最多增长到 10(因为 64 位整数的位数远大于题目范围),循环次数是常数级别,整体时间复杂度为 O(1)(更准确地讲是 O(d),d 为位数,极小)。 - 字符串转换和字符访问也是 O(d),d ≤ 10,所以总体非常快。
空间复杂度
- 常数空间,主要开销是将
num
转成字符串,其长度 ≤ 10,空间复杂度 O(1)。
总结
本题的关键在于不要去拼接整个无限序列,而是按“位段”跳跃定位。用 Int64
做中间计算以避免溢出,计算目标数字后取对应的字符即可。该方法简单、稳健、效率高,适合在工程场景中直接使用。若你需要进一步无字符串取位,也可以通过数学(整除/取余)实现,但字符串方式更直观、代码更简洁。