求数字序列中的第n位对应的数字

文章目录

  • 题目
  • 思路
  • 代码
  • 复杂度分析
  • 致谢


题目

数字以0123456789101112131415…的格式序列化到一个字符序列中。在这个序列中,第5位(从下标0开始计数)是5,第13位是1,第19位是4,等等。

请写一个函数,求任意第n位对应的数字。


思路

我们注意到,如果:

  1. 将 01234567891⋯ 中的每一位称为 ,记为 n
  2. 将 10,11,12,⋯ 称为 数字 ,记为 num
  3. 数字 10 是一个两位数,称此数字的 位数2 ,记为 digit
  4. digit 位数的起始数字(即:1,10,100,⋯),记为 start
  5. 个位数共有 1 ~ 9 九个数字,称个位数的 数字数量9 ,记为 num_count
  6. 十位数共有 10~99 90个数字,每个数字有两位,共占序列化 90*2=180 位,称十位数的 数位数量 为 180 , 记为 count

可以得到下表(个位数不计入0):

数字范围位数数字数量共占序列化多少位
1~9199
10~99290180
100~99939002700
……………………
start~enddigit9*start9 * start * digit

可以得到三个公式:

  1. 位数递推公式 digit = digit + 1
  2. 起始数字递推公式 start= start * 10
  3. 数字数量计算公式 num_count = 9 * start
  4. 数位数量计算公式 count = 9 * start * digit

我们可以将求 第n位对应的数字 求解过程分为以下几步:

  1. 确定 n 所在 数字位数digit
  2. 确定 n 所在的 数字num
  3. 确定 nnum 中的哪一位。

第一步:

在几位数的范围内?

while(n>count){n -= count;start *= 10;digit++;count = 9*start*digit;
}

第二步:

是哪个数字?

int num = start+(n-1)/digit;

为什么是 (n-1)/digit 而不是 n/digit

首先看一张图:
在这里插入图片描述

以两位数为例,当执行完 n=n-9 之后,n 与各个两位数数位的对应关系如上图所示。以13为例,当我们知道 n=7(13中的1) 时,由 n/2=3 可得 n 是十位数起始数字之后的第三个,但是当 n=8(13中的3) 时,由 n/2=4 得到 n 对应的是十位数起始数字之后的第四个,这是错误的,十位数起始数字之后的第四个是 14 而非 13 ,因此计算时需要将 n 进行 减一操作,因为我们将起始数字看作 第0个数 而非 第1个数

为什么是 (n-1)/digit 而不是 n/digit-1

仍然以两位数为例,当 n 对应非起始数字时,两种算法是没有区别的,但是当 n 对应起始数字时,不论 n=0 or n=1(n-1)/digit 的计算结果都为 0 ,而 n/digit-1 的计算结果都为 -1(n-1)/digit 对应的数字 num = start + 0 = 10 ,正确;而 n/digit-1 对应的数字 num = start + (-1) = 9 ,变成了个位数,错误。

总而言之,对 n 进行 减一操作 是因为 起始数字应视为 start + 0,虽然它是两位数里面的 第一个 数字,但是我们在公式里说的 第几个该数字相对于起始数字 而言的,因此要统统减一。而之所以不是 先除再减 而是 先减再除 是因为 要考虑逻辑顺序对起始数字的影响

第三步:

处于对应数字的哪一位?

num = s[(n-1)%digit] - '0';

(n-1)%digit 计算的便是对应的 数位 ,n减一的原因和第二步中相同,不再赘述。


代码

class Solution {
public:int findNthDigit(int n) {long long start=1, count=9, digit=1;while(n>count){n -= count;start *= 10;digit++;count = 9*start*digit;}int num = start+(n-1)/digit;//所在数字string s = to_string(num);num = s[(n-1)%digit] - '0';//处于所在num的哪一位return num;}
};

复杂度分析

时间复杂度 O(logn) : 所求数位 n 对应数字 num 的位数 digit 最大为 O(log n) ;第一步最多循环 O(log n) 次;第三步中将 num 转化为字符串使用 O(log n) 时间;因此总体为 O(log n)

空间复杂度 O(logn) : 将数字 num 转化为字符串 str(num) ,占用 O(logn) 的额外空间。


致谢

思路中的部分想法、复杂度分析源于K神的题解,鞠躬致谢。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/443787.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

一学就废的归并排序

文章目录其他与排序有关的文章原理代码实现复杂度分析其他与排序有关的文章 一学就废的三种简单排序【冒泡、插入、选择】 原理 归并排序(Merge sort): 归并排序对元素 递归地 进行 逐层折半分组,然后从最小分组开始&#xff0c…

树状数组的相关知识 及 求逆序对的运用

文章目录树状数组概念前缀和和区间和树状数组原理区间和——单点更新前缀和——区间查询完整代码离散化sort函数unique函数去重erase函数仅保留不重复元素通过树状数组求逆序对树状数组概念 树状数组又名二叉索引树,其查询与插入的复杂度都为 O(logN),其…

二叉搜索树相关知识及应用操作

文章目录概念查找二叉搜索树的第k大节点概念 二叉查找树(Binary Search Tree),(又名:二叉搜索树,二叉排序树)——它或者是一棵空树,或者是具有下列性质的二叉树: 若它的…

二叉树相关知识及求深度的代码实现

文章目录树二叉树满二叉树和完全二叉树二叉树的性质代码实现求二叉树的深度树 树是一种非线性的数据结构,它是由n个有限结点组成一个具有层次关系的集合。 树的相关名词: 根节点:没有前驱结点的结点。父节点,子节点&#xff1a…

大端小端存储模式详解及判断方法

文章目录大小端模式的概念两种模式出现原因两种模式的优劣大小端的应用情景判断机器的字节序大小端模式的概念 当我们查看数据在内存中的存储情况时,我们经常会发现一个很奇怪的现象,什么现象呢? int main() {int i 12;return 0; }数据在内…

Linux 内存管理 | 物理内存、内存碎片、伙伴系统、SLAB分配器

文章目录物理内存物理内存分配外部碎片内部碎片伙伴系统(buddy system)slab分配器物理内存 在Linux中,内核将物理内存划分为三个区域。 在解释DMA内存区域之前解释一下什么是DMA: DMA(直接存储器访问) 使用物理地址访问内存&am…

Linux 内存管理 | 虚拟内存管理:虚拟内存空间、虚拟内存分配

文章目录虚拟地址空间用户空间内核空间用户空间内存分配malloc内核空间内存分配kmallocvmalloc虚拟地址空间 在早期的计算机中,程序是直接运行在物理内存上的,而直接使用物理内存,通常都会面临以下几种问题: 内存缺乏访问控制&a…

Linux | 编译原理、gcc的命令参数、自动化构建工具 make/Makefile

文章目录编译原理预处理编译汇编链接gcc的常用命令参数make 和 Makefile 的概念make的运行通配符自动化变量伪目标.PHONE:【命令】编译原理 在解释 makefile 前,首先解释一下 .c 文件变成 .exe 文件要经过的四个步骤——预处理、编译、汇编和链接(参考来…

Linux | 进程概念、进程状态(僵尸进程、孤儿进程、守护进程)、进程地址空间

文章目录进程和程序操作系统如何控制和调度程序进程控制块–PCB子进程进程状态僵尸进程孤儿进程守护进程(精灵进程)进程地址空间引言页表进程和程序 程序: 一系列有序的指令集合(就是我们写的代码)。进程:…

Linux 进程控制 :进程创建,进程终止,进程等待,程序替换

文章目录进程创建进程等待程序替换进程终止进程创建 fork函数: 操作系统提供的创建新进程的方法,父进程通过调用 fork函数 创建一个子进程,父子进程代码共享,数据独有。 当调用 fork函数 时,通过 写时拷贝技术 来拷贝…

Linux 内存管理 | 连续分配方式 和 离散分配方式

文章目录前言连续分配单一连续分配分区式分配固定分区分配动态分区分配可重定位分区分配离散分配分段分页多级页表快表(TLB)段页式Linux前言 Linux 内存管理 | 虚拟内存管理:虚拟内存空间、虚拟内存分配 Linux 内存管理 | 物理内存、内存碎片、伙伴系统、SLAB分配器…

操作系统 | 用户态和内核态的切换(中断、系统调用与过程(库函数)调用)

文章目录中断过程调用系统调用过程调用和系统调用的区别中断 用户态、内核态之间的切换是怎么实现的? 用户态→内核态 是通过中断实现的。并且 中断是唯一途径 。核心态→用户态 的切换是通过执行一个特权指令,将程序状态字 (PSW) 的标志位设置为 用户态 。 中断…

管道实现父子进程的信息传递(二)【标准流和其文件描述符、fwrite函数、perror函数】

文章目录代码实现标准流 和 标准流文件描述符代码中用到的函数fwrite()perror()在复习进程间的通信方式时又写了一遍,和 管道实现父子进程的信息传递(一)【fork函数、pipe函数、write/read操作、wait函数】 的区别不是特别大,只是…

命名管道实现进程的信息传递【mkfifo函数、open函数】

文章目录代码实现mkfifo函数open函数代码实现 #include<fcntl.h> // open() #include<sys/wait.h> // wait() #include<sys/types.h> // mkfifo() #include<sys/stat.h> // mkfifo() #include<iostream> #include<unistd.h> // fork()usi…

Linux 进程 | 进程间的通信方式

文章目录管道匿名管道 pipe命名管道 FIFO共享内存共享内存的使用流程&#xff1a;消息队列信号量套接字在之前的博客中讲过&#xff0c;虚拟空间出现的其中一个目的就是解决 进程没有独立性&#xff0c;可能访问同一块物理内存 的问题。因为这种独立性&#xff0c;进程之间无法…

Linux网络编程 | socket介绍、网络字节序与主机字节序概念与两者的转换、TCP/UDP 连接中常用的 socket 接口

文章目录套接字socket 地址通用 socket 地址专用 socket 地址网络字节序与主机字节序地址转换TCP/UDP 连接中常用的 socket 接口套接字 什么是套接字&#xff1f; 所谓 套接字 (Socket) &#xff0c;就是对网络中 不同主机 上的应用进程之间进行双向通信的端点的抽象。 UNIX/L…

网络协议分析 | 传输层 :史上最全UDP、TCP协议详解,一篇通~

文章目录UDP概念格式UDP如何实现可靠传输基于UDP的应用层知名协议TCP概念格式保证TCP可靠性的八种机制确认应答、延时应答与捎带应答超时重传滑动窗口滑动窗口协议后退n协议选择重传协议流量控制拥塞控制发送窗口、接收窗口、拥塞窗口快速重传和快速恢复连接管理机制三次握手连…

JDom,jdom解析xml文件

1.要解析的文件模板如下&#xff1a; <?xml version"1.0" encoding"GBK"?> <crsc> <data><举报信息反馈><R index"1"><举报编号>1</举报编号><状态>1</状态><答复意见>填写…

网络协议分析 | 应用层:HTTP协议详解、HTTP代理服务器

文章目录概念URLHTTP协议的特点HTTP协议版本格式请求报文首行头部空行正文响应报文首行头部空行正文Cookie与SessionHTTP代理服务器正向代理服务器反向代理服务器透明代理服务器概念 先了解一下 因特网&#xff08;Internet&#xff09; 与 万维网&#xff08;World Wide Web&…

MySQL命令(一)| 数据类型、常用命令一览、库的操作、表的操作

文章目录数据类型数值类型字符串类型日期/时间类型常用命令一览库的操作显示当前数据库创建数据库使用数据库删除数据库表的操作创建表显示当前库中所有表查看表结构删除表数据类型 mysql 的数据类型主要分为 数值类型、日期/时间类型、字符串类型 三种。 数值类型 数值类型可…