力扣每日一题0323-440. 字典序的第K小数字

147 阅读1分钟

“Offer 驾到,掘友接招!我正在参与2022春招打卡活动,点击查看活动详情。”

给定整数 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

字典树

字典树指的就是通过前序去进行排序,以第一位的数进行排序后,依次对后面位数的数进行排序,

比如 10 < 9,因为 10 的前缀是 1,比 9 小。

再比如 112 < 12,因为 112 的前缀 11 小于 12。

这样排序下来,会跟平常的升序排序会有非常大的不同。先给你一个直观的感受,一个数乘 10,或者加 1,后者会更大。

image.png

每一个节点都拥有 10 个孩子节点,因为作为一个前缀 ,它后面可以接 0~9 这十个数字。而且你可以非常容易地发现,整个字典序排列也就是对十叉树进行先序遍历。1, 10, 100, 101, ... 11, 110 ...

回到题目的意思,我们需要找到排在第k位的数。找到他的排位,需要搞清楚三件事情:

  • 怎么确定一个前缀下所有子节点的个数?
  • 如果第 k 个数在当前的前缀下,怎么继续往下面的子节点找?
  • 如果第 k 个数不在当前的前缀,即当前的前缀比较小,如何扩大前缀,增大寻找的范围?
  1. 确定指定前缀下所有子节点数 现在的任务就是给定一个前缀,返回下面子节点总数。

    我们现在的思路就是用下一个前缀的起点减去当前前缀的起点,那么就是当前前缀下的所有子节点数总和。

    //prefix是前缀,n是上界
    var getCount = (prefix, n) => {
        let cur = prefix;
        let next = prefix + 1;//下一个前缀
        let count = 0;
        //当前的前缀当然不能大于上界
        while(cur <= n) {
            count += next - cur;//下一个前缀的起点减去当前前缀的起点
            cur *= 10; 
            next *= 10;
            // 如果说刚刚prefix是1,next是2,那么现在分别变成10和20
            // 1为前缀的子节点增加10个,十叉树增加一层, 变成了两层
            
            // 如果说现在prefix是10,next是20,那么现在分别变成100和200,
            // 1为前缀的子节点增加100个,十叉树又增加了一层,变成了三层
        }
        return count;//把当前前缀下的子节点和返回去。
    }
    

    当然,不知道大家发现一个问题没有,当 next 的值大于上界的时候,那以这个前缀为根节点的十叉树就不是满十叉树了啊,应该到上界那里,后面都不再有子节点。因此,count+=next−cur 还是有些问题的,我们来修正这个问题:

    count += Math.min(n+1, next) - cur;
    
  2. 第k个数在当前前缀下 现在无非就是往子树里面去看。

    prefix这样处理就可以了。

    prefix *= 10
    

    3.第k个数不在当前前缀下 说白了,当前的前缀小了嘛,我们扩大前缀。

    prefix ++;
    

完整代码展示:

/**
 * @param {number} n
 * @param {number} k
 * @return {number}
 */
var findKthNumber = function(n, k) {
  let getCount = (prefix, n) => {
    let count =  0;
    for(let cur = prefix, next = prefix + 1; cur <= n; cur *= 10, next *= 10) 
      count += Math.min(next, n+1) - cur;
    return count;
  }
  let p = 1;
  let prefix = 1;
  while(p < k) {
    let count = getCount(prefix, n);
    if(p + count > k) {
      prefix *= 10;
      p++;
    } else if(p + count <= k) {
      prefix ++;
      p += count;
    }
  }
  return prefix;
};