leetcode
leetcode 401 ~ 450
字典序的第K小数字

字典序的第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

代码结果

运行时间: 24 ms, 内存: 16.0 MB


/*
 * 思路:
 * 使用Java Stream流的方式计算。
 * 我们首先生成从1到n的范围内的整数流,然后使用sorted对其进行字典序排序。
 * 取第k个元素即可。
 */
import java.util.stream.IntStream;
public class Solution {
    public int findKthNumber(int n, int k) {
        return IntStream.rangeClosed(1, n)
                .mapToObj(String::valueOf)
                .sorted()
                .mapToInt(Integer::parseInt)
                .skip(k - 1)
                .findFirst()
                .getAsInt();
    }
}

解释

方法:

这个题解使用了一种巧妙的方法来找到字典序第k小的数字。主要思路是:从1开始,通过计算当前前缀下所有数字的个数,判断第k个数字是否在当前前缀下。如果在当前前缀下,则第k个数字的第一位就是当前前缀;如果不在,则将前缀加1,继续查找下一个前缀。重复这个过程,直到找到第k个数字。

时间复杂度:

O((log n)^2)

空间复杂度:

O(1)

代码细节讲解

🦆
解法中提到的'前缀'是具体指什么?在字典序问题中,前缀如何影响数字的排列?
在这个解法中,'前缀'指的是数字序列中的起始部分,这可以是一个或多个数字的组合。例如,如果前缀是'123',那么以这个前缀开头的数字包括123, 1230, 1231, ... 等等。在字典序问题中,前缀决定了数字的排列顺序,所有以同一前缀开始的数字会在字典序中连续出现,且按照从小到大的顺序排列。
🦆
在计算每个前缀下的数字个数时,为什么使用区间[interval[0], interval[1])的方式,并且每次区间扩大10倍?
这种计算方法是为了能够高效地估算出在指定前缀下的数字个数。区间 [interval[0], interval[1]) 表示以当前前缀开始直到下一个前缀开始之前的所有可能数字。例如,前缀为1的区间初值是[1, 2),代表数字1到1结束之前的所有数字。在每次迭代中,区间扩大10倍,例如[10, 20),[100, 200),这样做是因为每个数字都可以扩展出10个更长的数字(通过在末尾添加0到9),从而形成一个完整的10叉树结构,该结构帮助我们快速计算出在当前前缀下所有可能的数字个数。
🦆
为何在确定k不在当前前缀下时,选择将前缀加1而不是其他操作?这样的操作对于缩小查找范围有什么特殊的意义吗?
当确定k不在当前前缀下时,意味着我们需要跳过当前前缀下的所有数字,寻找下一个可能的前缀。将前缀加1是逻辑上的下一步,因为在字典序中,下一个可能的较大前缀正是当前前缀加1(例如从'1'变到'2')。这样做可以有效地将搜索范围缩小到下一个前缀的字典序区域,避免不必要的搜索,从而提高查找效率。
🦆
当确定第k个数字在当前前缀下时,为什么要将结果乘以10,并将k减去1,而不是直接减去cnt?这样做的目的是什么?
当确定第k个数字在当前前缀下时,将结果乘以10是因为我们需要进一步细化搜索,探索以当前前缀作为更高位数的新前缀的更具体的数字。例如,如果当前前缀是1,下一步我们需要探索10, 11, 12, ... 等等。将k减去1是因为我们已经确定了一个数字的位置(当前前缀自身),接下来需要在新的更具体的前缀下找到第k-1个数字。这种方法保持了算法的递归性质,使我们能够逐步逼近目标数字。

相关问题