“Offer 驾到,掘友接招!我正在参与2022春招系列活动-刷题打卡任务,点击查看活动详情。”
一、题目描述:
440. 字典序的第K小数字
给定整数 n 和 k,返回 [1, n] 中字典序第 k 小的数字。
示例 1:
输入: n = 13, k = 2
输出: 10
解释: 字典序的排列是 [1, 10, 11, 12, 13, 2, 3, 4, 5, 6, 7, 8, 9],所以第二小的数字是 10。
示例 2:
输入: n = 1, k = 1
输出: 1
提示:
1 <= k <= n <= 109
二、思路分析:
首先什么是字典序?
例如 为什么 112 放在 12 前面?因为首先他们的位数不一样,比较的前缀位数就位最小的位数,即比较 11 和12 ,故112放在12前面。
再看 为什么1 在10前面?因为虽然他们的前缀一样,但1的位数比10少。当位数一样时即10和11,比较最开始不相同的位数的值的大小。
构建字典树
通过上面的规则,我们可以知道,1可以组成一个前缀,有1019;10可以组成前缀,有100109;100可以组成前缀有 10001009 ......即每个数作为前缀都有下一位都有09,而组成的数又可以作为前缀,我们可以用一个10叉树来描述他们
可以发现 同一层的数他们的位数相同,进行先序遍历,得到的就是字典序
求字典树结点下的所有子节点(包括该节点)
我们要判断第k个数在哪,则必须知道每个前缀下的子节点数目。
我们按每层结点数相加最后得到结果,那么我们如何得到该层的节点数呢?因为它可能是不满的。对于前缀1,它的同层下一个前缀为1+1=2,节点数number+2-1。前缀1子节点最先是10=1* 10,前缀2子节点最先为20=2* 10,若子节点是满的则count+两者直接相减,否则即n在当前位置,要包含n故count+n+1-10。
代码如下
const getNumber=function(prefix,n){
let cur=prefix,next=prefix+1,number=0
while(cur<=n){
number+=Math.min(n+1,next)-cur
cur*=10
next*=10
}
return number
}
判断第k小数字位置
由题可知最开始第一小的数字为1,用p表示第p小的数字,用prefix表示当前前缀,prefix=1
首先根据n的大小,判断当前prefix下子节点的个数number(包含第p小的数字),故p+number-1表示已经遍历的数字范围+下一个区域范围,
- 若k<=p+number-1,则k肯定在这个范围里面,prefix*=10,p++,表示从当前前缀第一个子节点缩小范围,
- 否则,k不在这个范围里面,要扩大,故prefix++,p+=number,而不是加number-1,因为加number-1只表示当前前缀子节点的最后一个节点,并没有进入新的前缀
代码如下
let p=1,prefix=1
while(p<k){
let number=getNumber(prefix,n)
if(k>=p+number){
prefix++
p+=number
}else{
prefix*=10
p++
}
}
三 Ac代码
/**
* @param {number} n
* @param {number} k
* @return {number}
*/
var findKthNumber = function(n, k) {
const getNumber=function(prefix,n){
let cur=prefix,next=prefix+1,number=0
while(cur<=n){
number+=Math.min(n+1,next)-cur
cur*=10
next*=10
}
return number
}
let p=1,prefix=1
while(p<k){
let number=getNumber(prefix,n)
if(k>=p+number){
prefix++
p+=number
}else{
prefix*=10
p++
}
}
return prefix
};
总结
学会了 字典序,如何构建字典树,如何求字典树节点的子节点数