[ 字典树 ]440. 字典序的第K小数字

225 阅读1分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第20天,点击查看活动详情

每日刷题 2021.04.20

题目

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

解题思路

  • 根据题意分析:主要分两部分来处理
    • 计算一个节点下有多少节点(包括当前节点)
    • 类似的前序遍历(VLR)来计算k位置的值

如何计算节点下有多少个节点?

  • 简单画图可以发现一个节点,比如1,子节点是10-19,这中间的值字典表中都要小于2。
  • 继续往后推,10节点的子节点是110-119,这些节点小于12,而12的子节点是120-129,以此类推。
  • 可以得到某个节点的子节点n的所有子节点是 n10+9 - n10 + (n10+9)10+9 - (n1010)。
  • 每一层的最小值都是上一层的最小值10,最大值是上一层的最大值10+9。
  • 注意边界是n,如果最小节点小于n则说明已经统计了所有节点了,而且需要注意n大于最小值且小于最大值的情况。

如何前序遍历(VLR)

  • 根据题意前序遍历的结果就是排序之后的结果,取k位置的值就行了。
  • 有了第一部分,我们就能方便的算出一个节点有多少节点了。所以先获取最开始节点有多少个子节点。
    • 如果子节点数大于k,也就是k在当前节点内,则往下遍历(LR),这个时候需要节点*10获取子节点的最小值,注意k需要-1,因为要排掉当前节点(V)。
    • 如果子节点小于k,也就是说k在当前节点后面,当前节点+1,k减去当前节点的节点数。
  • k等于0的时候,即找到节点。

AC代码

var findKthNumber = function(n, k) {
  const getCount = (root, n) => {
    let result = 0
    let f = root
    let l = root
    while (f <= n) {
      result += Math.min(n, l) - f + 1
      f *= 10
      l *= 10
      l += 9
    }
    return result
  }
  let cur = 1
  k -= 1
  while (k > 0) {
    const count = getCount(cur, n)
    if (count > k) {
      cur *= 10
      k -= 1
    } else {
      cur += 1
      k -= count
    }
  }
  return cur
};

总结

  • 字典树比较复杂,常常在必须要用的时候才使用。