

文章目录
摘要
这道题让你在无限整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...] 里,把每个数按顺序拼成一条长长的数字串,然后返回第 n 位上的那个数字(0~9)。比如 n=3 就是第 3 个字符,即 "3";n=11 时,序列是 "1234567891011...",第 11 位是 10 里的那个 "0"。暴力做法是逐个数字拼字符串再数到第 n 位,n 最大到 2^31-1 会超时,所以要用「按位数分段」:先算出一位数、两位数、三位数......各占多少位,确定第 n 位落在哪一段、对应哪个数、以及是该数的第几位,最后取出那一位数字即可。下面用 Swift 实现并说明分段和取位的细节。

描述
给你一个整数 n,请你在无限整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...] 中找出并返回第 n 位上的数字。这里「第 n 位」指的是把序列里每个数依次拼成一个大串后,从左到右数第 n 个字符(从 1 开始计数),该字符一定是 0~9 中的某一个。
示例 1:
输入:n = 3
输出:3
解释:序列拼成 "123456789...",第 3 位是 "3"。
示例 2:
输入:n = 11
输出:0
解释:序列为 1,2,3,4,5,6,7,8,9,10,11,...,拼成 "1234567891011...",第 11 位数字是 10 里的 "0"。
提示:
- 1 <= n <= 2^31 - 1
核心思路:按「数字的位数」分段。一位数 1~9 共 9 个数、占 9 位;两位数 10~99 共 90 个、占 180 位;三位数 100~999 共 900 个、占 2700 位......先确定第 n 位落在哪一段,再算出是这一段里的第几个数、以及是该数的第几位,最后从该数中取出对应位返回。

题解答案
swift
class Solution400 {
func findNthDigit(_ n: Int) -> Int {
var n = n
var width = 1
var base = 1
var count = 9
while n > count {
n -= count
width += 1
base *= 10
count = 9 * base * width
}
let numIndex = (n - 1) / width
let digitIndex = (n - 1) % width
let number = base + numIndex
let s = String(number)
let index = s.index(s.startIndex, offsetBy: digitIndex)
return Int(String(s[index]))!
}
}
题解代码分析
为什么要按位数分段
序列可以看成:先写所有一位数(1~9),再写所有两位数(10~99),再写三位数(100~999)......每一段里,数的个数和每个数的位数都是固定的。一位数有 9 个,每个 1 位,共 9 位;两位数有 90 个,每个 2 位,共 180 位;三位数有 900 个,每个 3 位,共 2700 位。一般地,位数为 width 的那一段,最小的数是 10^(width-1)(即代码里的 base),个数是 9 * base,总位数是 9 * base * width。用 n 不断减去当前段的总位数,减不动时,第 n 位就落在当前这一段里,这样就不用真的去拼字符串,也能在 O(位数) 内定位到「第几个数、第几位」。
循环在做什么
一开始 width = 1、base = 1、count = 9,表示「一位数这一段一共有 9 位」。只要 n > count,说明第 n 位还在后面,就减去这 9 位,然后进入二位数:width = 2、base = 10、count = 9 * 10 * 2 = 180。再若 n 还大于 180,再减、再进三位数......直到某一段的 count 不小于当前的 n,此时 n 已经变成「在当前这一段内,从 1 开始数的第几位」。
如何算出是哪个数、第几位
当前段里,每个数占 width 位,所以「第 n 位」对应的是这一段里第 (n-1)/width 个数(从 0 开始数),且是该数的第 (n-1)%width 位(从左往右、从 0 开始)。这一段最小的数是 base,所以目标数 number = base + (n-1)/width。然后把 number 转成字符串,取下标为 (n-1)%width 的字符,转成数字返回即可。用字符串取位可以避免手写「求某数从左数第 k 位」的除法和取模,代码更直观、也不容易搞错进制。
示例 n=3 的过程
一开始 width=1,base=1,count=9。n=3 不大于 9,不进入循环。numIndex=(3-1)/1=2,digitIndex=(3-1)%1=0,number=1+2=3,即第 3 位落在数字 3 上,且是 3 的第 0 位(即最高位),也就是 "3",返回 3。
示例 n=11 的过程
width=1,count=9,n=11>9,n 减 9 变成 2,width=2,base=10,count=180。n=2 不大于 180,退出循环。numIndex=(2-1)/2=0,digitIndex=(2-1)%2=1,number=10+0=10,即第 11 位落在数字 10 上,且是 10 的第 1 位(从左数第二位),即 "0",返回 0。
示例测试及结果
示例 1:n = 3
序列前缀为 "123456789...",第 3 位是数字 3 本身,输出 3。
示例 2:n = 11
一位数占前 9 位,第 10、11 位来自 10,即 "1" 和 "0",第 11 位为 0。
示例 3:n = 1 或 n = 9
第 1 位是 "1",第 9 位是 "9",均在一位数段内,输出 1 和 9。
示例 4:刚好跨段
n=10 时,第 10 位是 10 的十位 "1";n=12 时是 11 的个位 "1"。用上述公式可验证 numIndex、digitIndex 与 number 均正确。
示例 5:较大的 n(多位数段)
例如 n 落在三位数、四位数段时,逻辑相同:先减掉所有更短段的总位数,再在当前段内用 (n-1)/width 和 (n-1)%width 定位到具体数字和位,取字符转成数字返回。
时间复杂度
循环次数等于「第 n 位所在数字的位数」,即 O(log n) 级别(因为位数约为 log₁₀(n))。循环内只有常数次加减乘除和赋值。定位到 number 后,转字符串和按下标取字符也是 O(位数),即 O(log n)。整体 O(log n)。
空间复杂度
只用了若干 Int 变量和一次 String(number),字符串长度等于当前数的位数,即 O(log n)。没有递归、没有和 n 同规模的数组,空间 O(log n)。
与实际场景的结合
这类「在无限序列里按位置取元素」的问题,在编码、索引、分页里会碰到。比如把一串连续 ID 或编号看成序列,问「第 k 个编码单元对应哪一段、哪一位」;或者在某些压缩、编码方案里,需要在不展开全文的情况下定位到第 n 个符号。掌握「按段计数 + 段内偏移」的思路,就可以在不真正构造整条序列的前提下 O(log n) 回答单次查询,适合 n 很大的情况。
总结
LeetCode 400 第 N 位数字的做法是:把序列按数字位数分成多段(一位、两位、三位......),先根据 n 确定第 n 位落在哪一段,再在该段内用 (n-1)/width 得到是第几个数、用 (n-1)%width 得到是该数的第几位,算出目标数字后转成字符串取对应位字符即可得到 0~9 的答案。时间 O(log n),空间 O(log n),无需真正拼接整条序列即可处理 n 最大到 2^31-1 的规模。