LeetCode 400 第 N 位数字


文章目录

摘要

这道题让你在无限整数序列 [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 = 1base = 1count = 9,表示「一位数这一段一共有 9 位」。只要 n > count,说明第 n 位还在后面,就减去这 9 位,然后进入二位数:width = 2base = 10count = 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 的规模。

相关推荐
再难也得平2 小时前
力扣239. 滑动窗口最大值(Java解法)
算法·leetcode·职场和发展
摩尔曼斯克的海2 小时前
力扣面试题--双指针类
python·算法·leetcode
灰色小旋风2 小时前
力扣——第7题(C++)
c++·算法·leetcode
故事和你913 小时前
sdut-程序设计基础Ⅰ-实验二选择结构(1-8)
大数据·开发语言·数据结构·c++·算法·优化·编译原理
努力学算法的蒟蒻3 小时前
day106(3.7)——leetcode面试经典150
算法·leetcode·面试
Σίσυφος19003 小时前
PCL聚类 之 欧式聚类(最常用)
算法·机器学习·聚类
所谓伊人,在水一方3334 小时前
【Python数据科学实战之路】第12章 | 无监督学习算法实战:聚类与降维的奥秘
python·sql·学习·算法·信息可视化·聚类
像素猎人4 小时前
数据结构之顺序表的插入+删除+查找+修改操作【主函数一步一输出,代码更加清晰直观】
数据结构·c++·算法
季明洵4 小时前
二叉树的最小深度、完全二叉树的节点个数、平衡二叉树、路径总和、从中序与后序遍历序列构造二叉树
java·数据结构·算法·leetcode·二叉树