标签: 字典树
难度: Hard
给定整数 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
标签: 字典树
难度: Hard
给定整数 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
运行时间: 24 ms
内存: 16.0 MB
class Solution: def findKthNumber(self, n: int, k: int) -> int: res = 1 k -= 1 while k > 0: cnt = 0 interval = [res, res+1] while interval[0] <= n: cnt += min(n+1, interval[1]) - interval[0] interval = [10*interval[0], 10*interval[1]] if k >= cnt: res += 1 k -= cnt else: res *= 10 k -= 1 return res
这个题解使用了一种巧妙的方法来找到字典序第k小的数字。主要思路是:从1开始,通过计算当前前缀下所有数字的个数,判断第k个数字是否在当前前缀下。如果在当前前缀下,则第k个数字的第一位就是当前前缀;如果不在,则将前缀加1,继续查找下一个前缀。重复这个过程,直到找到第k个数字。
时间复杂度: O((log n)^2)
空间复杂度: O(1)
class Solution: def findKthNumber(self, n: int, k: int) -> int: res = 1 k -= 1 while k > 0: cnt = 0 interval = [res, res+1] while interval[0] <= n: # 计算当前前缀下的数字个数 cnt += min(n+1, interval[1]) - interval[0] # 将区间扩大10倍,向下遍历10叉树 interval = [10*interval[0], 10*interval[1]] if k >= cnt: # 第k个数字不在当前前缀下,前缀加1 res += 1 k -= cnt else: # 第k个数字在当前前缀下,第一位确定为当前前缀 res *= 10 k -= 1 return res
在这个解法中,'前缀'指的是数字序列中的起始部分,这可以是一个或多个数字的组合。例如,如果前缀是'123',那么以这个前缀开头的数字包括123, 1230, 1231, ... 等等。在字典序问题中,前缀决定了数字的排列顺序,所有以同一前缀开始的数字会在字典序中连续出现,且按照从小到大的顺序排列。
这种计算方法是为了能够高效地估算出在指定前缀下的数字个数。区间 [interval[0], interval[1]) 表示以当前前缀开始直到下一个前缀开始之前的所有可能数字。例如,前缀为1的区间初值是[1, 2),代表数字1到1结束之前的所有数字。在每次迭代中,区间扩大10倍,例如[10, 20),[100, 200),这样做是因为每个数字都可以扩展出10个更长的数字(通过在末尾添加0到9),从而形成一个完整的10叉树结构,该结构帮助我们快速计算出在当前前缀下所有可能的数字个数。
当确定k不在当前前缀下时,意味着我们需要跳过当前前缀下的所有数字,寻找下一个可能的前缀。将前缀加1是逻辑上的下一步,因为在字典序中,下一个可能的较大前缀正是当前前缀加1(例如从'1'变到'2')。这样做可以有效地将搜索范围缩小到下一个前缀的字典序区域,避免不必要的搜索,从而提高查找效率。
当确定第k个数字在当前前缀下时,将结果乘以10是因为我们需要进一步细化搜索,探索以当前前缀作为更高位数的新前缀的更具体的数字。例如,如果当前前缀是1,下一步我们需要探索10, 11, 12, ... 等等。将k减去1是因为我们已经确定了一个数字的位置(当前前缀自身),接下来需要在新的更具体的前缀下找到第k-1个数字。这种方法保持了算法的递归性质,使我们能够逐步逼近目标数字。