一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第19天,点击查看活动详情。
题目链接:400. 第 N 位数字
题目描述
给你一个整数 n ,请你在无限的整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...] 中找出并返回第 n 位上的数字。
提示:
示例 1:
输入:n = 3
输出:3
示例 2:
输入:n = 11
输出:0
解释:第 11 位数字在序列 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ... 里是 0 ,它是 10 的一部分。
题意整理
将无限的整数序列 [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, ...] 拼接成字符串 1 2 3 4 5 6 7 8 9 1 0 1 1... ,返回字符串第 n 位(如果下标从 0 开始,就是第 n - 1 位)数字。例如第 1 位为 1 第 11 位是 0。
解题思路分析
习惯性动作:首先确定题目数据范围 ,这里 n 的上界为 ,也就是 int 类型的最大值 2,147,483,647 ,涉及到 int 边界的时候需要注意溢出问题。很明显这个数据范围是不允许我们暴力解决的。
为了定位无限整数序列中的第 n 位数字的值,我们首先需要知道 第 n 位数字是在哪一个整数中 ,并且还需要知道 是所在整数的第几位 。如果能解决以上两个问题,我们就可以得到无限整数序列中的第 n 位数字的值。
问题一:如何确定第 n 位数字是在哪一个整数中
这里是这道题的核心难点,我们需要通过找规律的方式来确定:
1位数的范围是1到9,共有9个数,所有1位整数的位数之和是 。2位数的取值范围是10到99,共有90个数,所有2位整数的位数之和是 。3位数的取值范围是100到999,共有900个数,所有3位整数的位数之和是 。- ......
通过以上规律我们可以推广到一般情形从而得出结论:
x位数的范围是 到 ;- 共有 个数;
- 所有
x位数的位数之和是 。
我们可以通过不断增加整数的位数,通过求和得到总的位数和,当加上当前 x 位整数的位数和大于 n 时,就可以 判断第 n 位数字所在的整数是多少位的整数 ,我们可以得到 x 位整数的第一个数为 ,因为当前整数都为 x 位,所以可以直接利用除法得到第 n 位所在的具体数字是多少。
问题二:如何确定第 n 位数字是所在整数的第几位
当我们确定第 n 位数字是在哪一个整数中后,确定第 n 位数字是所在整数的第几位就很简单了,将剩余的位数用来确定第 n 位数字是在 x 位整数中的第几位即可。
解题的核心思想已经说完了,具体实现部分因人而异,不同的人有不同的实现方法,主要是要把解题的核心思想搞懂。
具体实现
-
确定第
n位数字所在整数是几位数:-
方法一:二分查找 ,使用二分的前提条件是需要单调性,因为所有不超过
x位整数的位数之和是关于x单调递增,因此是可以用二分查找的方法确定这个位数的值。对于不超过x位整数的所有位数之和小于n,则可以判断第n位所在整数的位数一定大于x位。否则第n位所在整数的位数一定小于等于x位。二分查找复杂度分析
- 时间复杂度: 。用 表示位数的上限,则有 。二分查找的执行次数是 ,每次执行的时间复杂度是 ,因此总时间复杂度是 。
- 空间复杂度:,仅需使用到常数空间。
-
方法二:直接计算 ,因为数据最大值的位数最多
9位,所以也可以不使用二分查找,而是根据规律直接计算。已知x位数共有 个,所有x位数的位数之和是 。使用d和count分别表示当前遍历到的位数和当前位数下的所有整数的位数之和,初始化d = 1,count = 9。不断将n减去当前整数位的位数之和d * count,然后将d加1,将count乘以10,直到n <= d * count,此时的d是目标数字所在整数的位数,n是所有d位数中从第一位到目标数字的位数。直接计算复杂度分析
- 时间复杂度:时间复杂度:。用
d表示第n位数字所在整数的位数,循环需要遍历d次,由于 ,因此时间复杂度是 。 - 空间复杂度:,仅需使用到常数空间。
- 时间复杂度:时间复杂度:。用
-
-
确定第
n位数字是在哪一个整数中的第几位在得到第
n位数字所在整数是几位数之后,这里用d表示。将不超过d - 1的整数的位数之和减去得到第n位数在所有d位数的序列中的下标,为了方便计算,将下标转换成从0开始记数。因为当前整数都为x位,所以可以直接利用除法得到第n位所在的具体数字是多少,再根据剩余的位数进行判断具体数值即可。
这里需要注意细节的处理,明确下标的指代。
代码实现
方法一:二分
class Solution {
public:
int findNthDigit(int n) {
//确定位数上下界
int low = 1, high = 9;
//二分确定位数
while (low < high) {
int mid = (high - low) / 2 + low;
if (totalDigits(mid) < n) {
low = mid + 1;
} else {
high = mid;
}
}
//得到位数d
int d = low;
//减去所有不超过d - 1位整数的位数和
int prevDigits = totalDigits(d - 1);
//注意这里下标指代
int index = n - prevDigits - 1;
//d位开始的数字
int start = (int) pow(10, d - 1);
//得到具体所在的整数
int num = start + index / d;
//明确第n位具体的值
int digitIndex = index % d;
int digit = (num / (int) (pow(10, d - digitIndex - 1))) % 10;
return digit;
}
//计算不超过length位的整数位数和
int totalDigits(int length) {
int digits = 0;
int curLength = 1, curCount = 9;
while (curLength <= length) {
digits += curLength * curCount;
curLength++;
curCount *= 10;
}
return digits;
}
};
方法二:直接计算
class Solution {
public:
int findNthDigit(int n) {
//1位的数字共9个 [1, 9]
//d表示当前遍历到的位数;count表示当前位数下的所有整数的位数之和
int d = 1, count = 9;
//找到n所在数字的位数
while(n > (long)d * count){
n -= d * count;
d++;
count *= 10;
}
//start为d位开始的数字
int start = pow(10, d - 1);
//index为目标数字在所有 d 位数中的下标,为了方便计算,将下标转换成从 0 开始记数。
int index = n - 1;
//num为n所在的数字
int num = start + (index / d);
//digitIndex为在数字num中的下标
int digitIndex = index % d;
//最后答案就是取出num中第digitIndex位数字
int ans = (num / (int) pow(10, d - digitIndex - 1)) % 10;
return ans;
}
};
总结
该题的核心难点在于 如何确定第 n 位数字是在哪一个整数中 ,解决这个难点问题的核心思想 是先确定第 n 位数字所在整数是几位数 ,这里是需要通过找规律的方式来确定。不用纠结于具体的实现方法,而是要把解题的核心思路搞清楚,无论是二分查找还是直接计算,其核心思想都离不开寻找第 n 位数字所在整数是几位数。
结束语
你是否经常感慨,好像别人的成功都来得很容易。事实上,别人受过的苦累我们并不知晓。所有光鲜的背后都意味着深深的自律。雕塑自己的过程必定伴随着疼痛与辛苦,可那一锤一凿的自我敲打,会让我们收获更好的自己。