LeetCode 字典序数字系列
放上两个题目:字典序排数(中等),字典序的第K小数字(困难)
首先说明什么是字典序:科班的同学应该都知道,在比较字符串的大小的时候是按照字典序来比较的。
例如:“abdcefg” 和 “abcdefg” 按字典序比较,结果应该是前者大于后者。因为相同长度字符串,从头到尾按字符比较,第三位 d d d > > > c c c.
对于不等长的字符串,也是这个较规则,如图:
同理,我们在数字上也按照字符串处理,如:123 >122、2>123.
1-13的字典序为: [1, 10, 11, 12, 13, 2, 3, 4, 5, 6, 7, 8, 9]
自然地我们引入字典树,以数字为例(我们如果用0作为根,把1-9统一到一棵树上(0-9十叉树),这棵树的先序遍历(深搜)就是字典序):
第一题:字典序排数(中等)
题目要求我们要输出字典序,按照前面的分析,我们可以直接递归来实现
class Solution {
public:
vector<int> ans;
void dfs (int cur, int n)
{
if(cur > n)
return;
ans.push_back(cur);
for (int i = 0; i <= 9; i++)
dfs(cur * 10 + i, n);
}
vector<int> lexicalOrder(int n) {
for (int i = 1; i <= 9; i++)
dfs(i, n);
return ans;
}
};
这种写法,在理解字典树之后,还是比较容易能写出来的。但是题目中要求了:
你必须设计一个时间复杂度为 O(n) 且使用 O(1) 额外空间的算法
此时题目就变成了规律题(从1-25来看):
[1,10,11,12,13,14,15,16,17,18,19,2,20,21,22,23,24,25,3,4,5,6,7,8,9]
( 1 ) (1) (1)首先我们肯定要从1
来开始,然后我们回想字典树,1
的下一层是1
的10
倍,如果当前层的第一个数10
没有超过n
,我们还要继续下一层100,``100>25
,所以和10
为同一个父节点的需要提前输出。
( 2 ) (2) (2)那么如何跳出此节点,去其他最高父节点(2-9)
,我们知道父节点的孩子都是0-9
,前面已经确定不会深度不会继续向下了,即19
接下来的2
可以以9
结尾为标志:num % 10 = 9
.同理,跳出此例子,119
的下一位为12
.
( 3 ) (3) (3)此外,我们要注意, ( 2 ) (2) (2)讨论的是这10
个节点全部输出之后的情况,来找父节点,那么n
是可能出现在0-9
之间的,如25
之后没有26、27、28、29
的,直接进入3
,所以我们要在 ( 2 ) (2) (2)的情况里加上num + 1 > n
也要跳。
至此分析结束,编写代码(c++)
class Solution {
public:
vector<int> lexicalOrder(int n) {
vector<int> ret(n);
int number = 1;//开始位
for (int i = 0; i < n; i++) {
ret[i] = number;//存入输出数组
if (number * 10 <= n) {
number *= 10;//确定深度
} else {
while (number % 10 == 9 || number + 1 > n) {//跳节点
number /= 10;
}
number++;//控制1-9,0已经在乘10存入了。
}
}
return ret;
}
};
第二题:字典序的第K小数字(困难)
如果数据量不是
10^9^
,这个题目也完全可以用第一题的两种写法来实现。
此时我们就需要统计,根据字典树及先序遍历,提供一个思路就是,从根节点(1)
,实现一个函数,来求得每个根节点下有多少节点在所给的n
范围内。如果求出来的此根节点下的总结点数小于k
,那么第k
个节点一定不再这个根节点的树上。然后再去看下一个根节点(2)
,按照以上步骤,如果还是小于k
,继续同一层的下一个根节点,如果大于k
,则第k
个数一定在这个根节点的树上。然后我们再来遍历深度。如(20)
,再重复以上的步骤,直到找到第k
个。
注意在整个过程中,我们要维护k的值!!!
class Solution {
public:
int getSteps(int curr, long n) {//得到节点curr下有多少个节点小于等于n
int steps = 0;
long first = curr;
long last = curr;
while (first <= n) {
steps += min(last, n) - first + 1;
first = first * 10;
last = last * 10 + 9;
}
return steps;
}
int findKthNumber(int n, int k) {
int curr = 1;//从1开始
k--;//维护K
while (k > 0) {//k=0时即第k个数
int steps = getSteps(curr, n);
if (steps <= k) {//不在curr节点的树上,去下一个同深度的根节点
k -= steps;//维护K
curr++;
} else {//在curr节点上,去curr的下一层。
curr = curr * 10;
k--;//维护K
}
}
return curr;
}
};