题目
Given an integer n, return all the numbers in the range [1, n] sorted in lexicographical order.
You must write an algorithm that runs in O(n) time and uses O(1) extra space.
来源:力扣(LeetCode)
链接:leetcode 0386
解析
第一眼:嗯,题目很短,来者不善
第二眼:nm,什么是lexicographical order?
第三眼:
O
(
n
)
O(n)
O(n)的要求有点变态
好吧又是一道我解不了的medium,折腾一个上午也没想明白怎么才能
O
(
n
)
O(n)
O(n)
下面进入正题:
- 什么叫做字典序(lexicographical order)
其实定义很简单,说起来是按照英文字典中的单次的排序,其实简单的理解就是对字符串比较大小然后按照升序排列 - 怎么做到
O
(
n
)
O(n)
O(n)
说到字符串排序,想来大家都蠢蠢欲动,直接转成字符串然后一波sort直接干掉。这当然是最直接的解法,但是它能做到 O ( n ) O(n) O(n)吗?并不能!不管你选什么排序算法(什么猴子排序、睡眠排序任你随便选什么排序算法,平均复杂度最好也就做到 O ( N l o g N ) O(NlogN) O(NlogN)),因此题目设计之初应该不是让我们用 s o r t sort sort来解决问题的,那到底该如何解决呢?
题解
我们来看两个示例
n = 13
输出:[1, 10, 11, 12, 2, 3, 4, 5, 6, 7, 8, 9]
n = 123
输出:[1, 10, 100, 11, 110, 111, 112, 113, …]
可以看到,这种排序方法其实有点类似于深度优先搜索的操作,即给定一个前缀数字(前缀数字有
1
,
2
,
3
,
4
,
5
,
6
,
7
,
8
,
9
1,2,3,4,5,6,7,8,9
1,2,3,4,5,6,7,8,9),我们需要把以它开头的所有数据都找出来并给出正确的排序。为什么说像是深度优先排序呢?看下图:
是不是有点DFS的意思了?那么这道题是否能用DFS来做呢?我们还得考虑,由于递归调用DFS会产生堆栈开销,但是题目要求我们额外的空间开销为
O
(
1
)
O(1)
O(1),因此这里不能使用递归DFS,但是迭代DFS总是可以的嘛,上代码:
class Solution:
def lexicalOrder(self, n: int) -> List[int]:
# define the result
result = [0] * n
# initial value must be 1
num = 1
# start to search with dfs
for i in range(n):
# put number to result
result[i] = num
# num * 10 until the result is greater than n, this is the left most path(in dfs)
if num * 10 <= n:
num *= 10
else:
# here we need to find the edge case, num % 10 is 9 when the subtree of current prefix is traversed
# or num + 1 > n means that the current node is the last one to traverse for current prefix
# both of them means the change of prefix
while num % 10 == 9 or num + 1 > n:
# move upward
num //= 10
# update number
num += 1
return result
说实话,看到官解的这个代码的时候,一种优雅的感觉油然而生(自己写的是什么shit…)。很简单,从最左侧开始一条条路走下去,走不动的时候就往上回一节,或者走到一个前缀的子树的尽头了(遇到了尾9)也往上回一步。
次优解
虽然使用递归的解法不是满足题意的解法,这里我们也把这种解法给出来
class Solution:
def lexicalOrder_dfs(self, n: int) -> List[int]:
# define the result list
result = []
# dfs in recursive format
def dfs(cur):
# edge case
if cur > n:
return
# store each result in result
result.append(cur)
for i in range(10):
dfs(cur * 10 + i)
for i in range(1, 10):
# dfs for each prefix
dfs(i)
return result
总结
字典树的定义已经决定了,该类问题的性质其实就是DFS,本题与leetcode 440 K-th Smallest in Lexicographical Order有着异曲同工之妙。我们将在下一篇文章中讨论该题。