1. 问题描述:
给定整数 n 和 k,找到 1 到 n 中字典序第 k 小的数字。注意:1 ≤ k ≤ n ≤ 10 ^ 9。
示例 :
输入:
n: 13 k: 2
输出:
10
解释:
字典序的排列是 [1, 10, 11, 12, 13, 2, 3, 4, 5, 6, 7, 8, 9],所以第二小的数字是 10。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/k-th-smallest-in-lexicographical-order
2. 思路分析:
① 这道题目是经典的数位统计的知识点的题目,对于这种题目的主要思路是按照每一位可能的情况进行考虑,对于这道题目来说需要考虑的情况还是比较多的,而且比较难想出来。我们可以按位考虑当前1~n数字的前缀,一开始的先考虑1的前缀,若k大于了1~n中1的前缀数目,说明答案的前缀肯定是比1大(前缀肯定是大于等于2的),否则也即k小于等于了1的前缀数目,说明答案的前缀肯定是在1里面找,并且前缀的位数应该是多一位的,也即考虑前缀为10的时候....在循环中依次根据k与当前的prefix数目来更新前缀。我们可以写一个函数f来实现1~n前缀为prefix的数目,这样我们可以调用f函数计算出1~n中当前前缀为prefix的数目count,通过k与count的大小关系确定前缀应该是下一个数字还是在当前前缀的基础上扩展多一位,也即若k > count说明答案的前缀大于当前的prefix,此时prefix += 1,并且k -= count,表示下一次计算的是前缀为2的时候的排名,当k <= count说明答案肯定是在prefix里面找,并且答案肯定不是prefix,这个时候k就要减去自己,也即需要减去1,并且prefix需要扩展一位,也即prefix *= 10。
② f的函数实现其实也不是特别难,我们可以举出具体的例子可以很快确定具体的实现思路。可以先将prefix与n这两个数字转为字符串,记A = str(n), B = str(prefix)
A :xxxxx
B :xxx
当B的前缀长度小于A的长度的时候,每一位上的数字可以随便填,举一个简单的例子,A = 12345, B = 12,那么当B的长度小于A的长度的时候可以分两种情况讨论,
12:1
120~129:10
1200~1299:100
所以当A的长度小于B的长度的时候(B的长度最多到A的长度-1)也1 + 10 + 100 ,使用一个变量在长度为len(A) - len(B)的循环中累加即可。当AB位数相等的时候需要判断A中与B等长度前缀的大小关系判断属于哪一种情况,此时又可以分为三种情况:
A = A[0:str(B)]在A中截取与B等长度的字符串,判断等长度的前提下AB的大小关系,若A == B说明B恰好是A的某一部分前缀,此时剩余的前缀数目为n - prefix * p + 1,结合上面的例子会更好理解一点,上面的例子中当B的长度等于A的长度减1的时候,也即A = 12345,B = 1234,p = 100,prefix = 12,所以我们用n - prefix * p + 1得到的就是考虑最后一位的情况。当A > B的时候那么剩下一位随便填即可,也即加上p即可。结合具体的例子会更好理解一点。
3. 代码如下:
class Solution:
# f计算1~n的数字中前缀为prefix的数字个数
def f(self, prefix: int, n: int):
# 将prefix, n转为字符串会比较好处理
A, B = str(n), str(prefix)
p = 1
res = 0
l = len(A) - len(B)
# 当B的位数小于A的时候那么剩余的位数随便填
for i in range(l):
res += p
p *= 10
# 截取字符串是为了比较两者等长的前提下的的前缀大小方便计算B中最后一位可能的情况
A = A[0:len(B)]
if A == B:
res += n - prefix * p + 1
elif A > B:
# A大于B的时候说明在长度为len(A)的前提下B的最后一位是随便填的
res += p
# 当A小于B不满足题目要求不管
return res
# 基本思想是一位位考虑前缀, 判断前缀属于哪一种情况
def findKthNumber(self, n: int, k: int) -> int:
prefix = 1
while k > 1:
count = self.f(prefix, n)
# 说明前缀肯定是在后面
if k > count:
prefix += 1
# 减去之前的前缀对应的数目
k -= count
else:
# 减去自己, 因为答案肯定不是prefix, 肯定是在后面
k -= 1
# 扩展前缀的位数
prefix *= 10
return prefix