LeetCode 400 - 第 N 位数字


文章目录

摘要

这道题看起来像是"在无限序列里随便数第 n 个字符",但要做到O(1) 空间、O(d) 时间(d = 位数长度,通常 ≤ 10)并不是直接依次拼接数字就能高效做的。我们需要利用"按位段(1位、2位、3位......)"来跳过大量数字,定位到目标数字,然后从数字中取出对应那一位。本文用 Swift 给出清晰的实现、细致代码解析与示例测试,便于在工程中直接复用。

描述

考虑无限整数序列:

txt 复制代码
"1234567891011121314..."

给定整数 n(1 ≤ n ≤ 2³¹-1),返回该序列第 n 位上的数字(单个十进制数字 0--9)。

举例:

  • n = 3 → 第 3 位是 3
  • n = 11 → 序列前 11 个字符是 12345678910,第 11 位是 0(来自数字 10)

题解答案

核心思路分三步:

  1. 按位数分段跳过:先看 1 位数(1...9)共 9 个数,占 9×1 = 9 位;再看 2 位数(10...99)共 90 个数,占 90×2 位,依次类推,直到 n 落在当前位段中。
  2. 确定具体数字:假定 n 最终落在 digit 位数段,段起始数字为 start = 10^(digit-1),则第 (n-1)/digit 个数字(从 0 开始)即为目标数 num = start + (n-1)/digit
  3. num 中取第 (n-1) % digit 个数字(从左到右),返回它的数值。

实现要点:用 Int64 做中间计算以免乘法溢出(n 最大可达 2³¹-1),取位时可用字符串或数学方式。下面给出可运行的 Swift 实现。

题解代码分析

  • 主要变量:

    • n64:将输入 n 转为 Int64,方便大数运算。
    • digit:当前位数(从 1 开始)。
    • count:当前位数段的数字个数,例如当 digit = 2count = 90(10...99)。
    • start:当前段起始数字,如 1, 10, 100, ...
  • 循环:当 n64 > count * digit 时说明目标不在当前段,减去这段的总位数后进下一段。

  • 最终 numstart + (n64 - 1) / digit 计算。再从 num 中提取对应的位(使用字符串更直观,也足够快,位数 ≤ 10)。

下面是完整代码(包含注释与示例测试)。

swift 复制代码
import Foundation

class 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 * count
            digit += 1
            count *= 10
            start *= 10
        }
        
        // 在当前位段中,第 (n64-1)/digit 个数(从 0 开始)
        let offset = (n64 - 1) / digit
        let 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'

示例测试及结果

上面示例结果:

txt 复制代码
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 做中间计算以避免溢出,计算目标数字后取对应的字符即可。该方法简单、稳健、效率高,适合在工程场景中直接使用。若你需要进一步无字符串取位,也可以通过数学(整除/取余)实现,但字符串方式更直观、代码更简洁。

相关推荐
STY_fish_20125 小时前
P11855 [CSP-J2022 山东] 部署
算法·图论·差分
myw0712055 小时前
湘大头歌程-Ride to Office练习笔记
c语言·数据结构·笔记·算法
H_BB5 小时前
算法详解:滑动窗口机制
数据结构·c++·算法·滑动窗口
Zero-Talent5 小时前
“栈” 算法
算法
橘子编程5 小时前
经典排序算法全解析
java·算法·排序算法
waeng_luo5 小时前
【鸿蒙开发实战】智能数据洞察服务:待回礼分析与关系维护建议算法
算法·ai编程·鸿蒙
风筝在晴天搁浅5 小时前
代码随想录 279.完全平方数
算法
不穿格子的程序员5 小时前
从零开始刷算法——字串与区间类经典题:前缀和 + 单调队列双杀
算法·前缀和·哈希表·双向队列·单调队列
坚持就完事了5 小时前
十大排序算法
数据结构·算法·排序算法
面试鸭5 小时前
2025 校招生最想去的公司,秋招薪资汇总
计算机·职场和发展·互联网