剑指 Offer(第2版)面试题 44:数字序列中某一位的数字
剑指 Offer(第2版)面试题 44:数字序列中某一位的数字
题目来源:
解法1:枚举
从 0 开始枚举每个数字,求出该数字的位数,累加起来。如果位数之和小于等于 n,继续枚举下一个数字。
当累加数位大于 n 时,那么第 n 位数字一定在这个数字中,我们再从该数字中找出对应的那一位。
代码:
class Solution
{
public:
int digitAtIndex(int n)
{
if (n < 10)
return n;
int cur = 0;
int length = 0;
while (length <= n)
{
length += digitLength(cur);
cur++;
}
for (int i = 0; i < length - n; i++)
cur /= 10;
return cur % 10;
}
// 辅函数
int digitLength(int x)
{
int len = 0;
while (x)
{
len++;
x /= 10;
}
return len;
}
};
复杂度分析:
时间复杂度:O(n)。
空间复杂度:O(1)。
很不幸,这样做超时了。
解法2:数学
一个一个枚举的找太慢了,我们需要跳着找。
我们以找序列的第 1001 位为例。
序列的前 10 位是 0 ~ 9 这 10 个只有一位的数字。显然第 1001 位在这 10 个数字之后,跳过这些一位数,我们从后面的序列找第 991(1001 - 10 = 991)位的数字。
接下来是 90 个二位数 10 ~ 99,总共 180 位。由于 991 > 180,所以再跳过这些二位数,继续找后面的第 811(991 - 180 = 811)位的数字。
接下来是 900 个三位数 100 ~ 999,总共 2700 位。由于 811 < 2700,所以第 811 位是某个三位数的一位。由于 811 = 3 * 270 + 1,这意味着第 811 位是从 100 开始的第 270 个数字,即 370 的中间一位,也就是 7。
代码:
class Solution
{
public:
int digitAtIndex(int index)
{
if (index < 0)
return -1;
int digits = 1;
while (true)
{
int numbers = countOfIntegers(digits);
if (index < (long long)digits * numbers)
return digitAtIndex(index, digits);
index -= digits * numbers;
digits++;
}
return -1;
}
int digitAtIndex(int index, int digits)
{
int number = beginNumber(digits) + index / digits;
int indexFromRight = digits - index % digits;
for (int i = 1; i < indexFromRight; i++)
number /= 10;
return number % 10;
}
// 辅函数 - 返回 digits 位数的总个数
int countOfIntegers(int digits)
{
if (digits == 1)
return 10;
int count = pow(10, digits - 1);
return 9 * count;
}
// 辅函数 - 返回 digits 位数的第一个数字
int beginNumber(int digits)
{
if (digits == 1)
return 0;
return pow(10, digits - 1);
}
};
PS:digits * numbers 可能会很大,比如 9 * 108,这样会爆 int,改为 long long。
复杂度分析:
时间复杂度:O(logn),所求数位 n 对应数字 num 的位数 digits 最大为 O(logn)。while 循环内最多 O(logn) 次。
空间复杂度:O(1)。
上面的是书上的版本,辅函数很多,下面是简化版本:
class Solution
{
public:
int digitAtIndex(int n)
{
int digit = 1; // 位数
long start = 1; // digit 位数的起始数字
long count = 9; // digit 位数的位数总和
while (count < n)
{
n -= count;
digit++; // 1, 2, 3, ...
start *= 10; // 1, 10, 100, ...
count = 9 * digit * start; // 9, 180, 2700, ...
}
// 所求数位在 digit 位数中,是从 start 开始的第 n 个数位
long num = start + (n - 1) / digit;
// 所求数位在数字 num 中的第 (n - 1) % digit 位
return to_string(num)[(n - 1) % digit] - '0';
}
};
对应题解:400. 第 N 位数字(清晰图解)
复杂度分析:
时间复杂度:O(logn),所求数位 n 对应数字 num 的位数 digit 最大为 O(logn);第一步最多循环 O(logn) 次;第三步中将 num 转化为字符串使用 O(logn) 时间;因此总体为 O(logn)。
空间复杂度:O(logn),将数字 num 转化为字符串 str(num) ,占用 O(logn) 的额外空间。