本文已参与「新人创作礼」活动,一起开启掘金创作之路。
| 每日一题做题记录,参考官方和三叶的题解 |
题目要求
【典型字越少事越大】
字典序
简单来讲就是把数字看成是英语单词一样排序,先按第一个字符排序,然后按第二个字符排序……长度短的靠前放。
也就是说靠前缀大小排序,这个排出来的顺序和十叉树的先序遍历是一样的。
思路
-
将题目过程拆分为:
确定前缀和在前缀值下找目标值节点。 -
定义一个
getCnt(x, limit)函数,用于计算内以为前缀的数的个数。记和的位数分别为、。- 位数小于的数均符合条件,直接计入,共个
- 位数等于的数中,取中长的前缀:
- :所有为前缀的数都大于;
- :[] 为符合条件的数,共个。
- :所有为前缀的数都小于,共个。
-
从最小的前缀开始枚举,枚举到前缀时,减去已经找到的符合条件的数,直至减为1得到目标值,记:
- :所有以为前缀的数都可以跳过,自增(跳到同一层隔壁兄弟节点),减去,找下一个数值比x大的前缀;
- :目标值前缀为,(跳到下一层),减去,找下一个字典序比x大的前缀。
Java
在实现getCnt时,采用子串的方式构建。
class Solution {
public int findKthNumber(int n, int k) {
int res = 1;
while (k > 1) {
int cnt = getCnt(res, n);
if(cnt < k) { //进入兄弟节点
k -= cnt;
res++;
}
else { //进入子树
k--;
res *= 10;
}
}
return res;
}
//以x为前缀数的个数
int getCnt(int x, int limit) {
String xv = String.valueOf(x), lv = String.valueOf(limit);
int xlen = xv.length(), llen = lv.length(), d = llen - xlen;
int pre = Integer.parseInt(lv.substring(0, xlen)); //limit中与x一样长的前缀
int cnt = 0;
//长度小于llen的数
for(int i = 0; i < d; i++)
cnt += Math.pow(10, i);
//长度为llen的数
if(pre > x) //所有以x为前缀的数都满足
cnt += Math.pow(10, d);
else if(pre == x) //部分满足
cnt += limit - x * Math.pow(10, d) + 1;
return cnt;
}
}
- 时间复杂度:,枚举前缀和getCnt的操作均复杂度均为
- 空间复杂度:,忽略子串生成的复杂度为
C++
在实现getCnt时,采用树结构思路,找当前层小于limit的数,不断向下一层(前缀加长一位)寻找。
class Solution {
public:
int findKthNumber(int n, int k) {
int res = 1;
while (k > 1) {
int cnt = getCnt(res, n);
if(cnt < k) { //进入兄弟节点
k -= cnt;
res++;
}
else { //进入子树
k--;
res *= 10;
}
}
return res;
}
//以x为前缀数的个数
int getCnt(int x, long limit) {
int cnt = 0;
long left = x, right = x; //当前层符合条件的最小(左)和最大(右)数
while(left <= limit) {
cnt += min(limit, right) - left + 1;
left = left * 10;
right = right * 10 + 9;
}
return cnt;
}
};
- 时间复杂度:
- 空间复杂度:
总结
读完要求是完全没有头绪的题目,解题思路不难,但要捋通顺还是花了点时间。
对于两种构建getCnt的方法,其底层逻辑相似,但一种利用字符串比较,另一种基于十叉树的层序遍历。
| 欢迎指正与讨论! |