剑指offer-17

剑指 Offer 17. 打印从1到最大的n位数

输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。

1
2
输入: n = 1
输出: [1,2,3,4,5,6,7,8,9]

解法

大数打印解法

实际上,本题的主要考点是大数越界情况下的打印。需要解决以下三个问题:

  1. 表示大数的变量类型

    • 无论是 short / int / long … 任意变量类型,数字的取值范围都是有限的。因此,大数的表示应用字符串 String 类型。
  2. 生成数字的字符串集

    • 使用 int 类型时,每轮可通过 +1 生成下个数字,而此方法无法应用至 String 类型。并且, String 类型的数字的进位操作效率较低,例如 “9999” 至 “10000” 需要从个位到千位循环判断,进位4次。
    • 观察可知,生成的列表实际上是 $n$位 $0-9$的 全排列,因此可避开进位操作,通过递归生成数字的 String 列表。
  3. 递归生成全排列:

    • 基于分治算法的思想,先固定高位,向低位递归,当个位已被固定时,添加数字的字符串。例如当 $n=2$时(数字范围 $1-99$),固定十位为 $0-9$,按顺序依次开启递归,固定个位 $0-9$,终止递归并添加数字字符串。

    image-20210608161806372

    根据以上方法,可初步编写全排列代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    class Solution:
    def printNumbers(self, n: int) -> [int]:
    def dfs(x):
    if x == n: # 终止条件:已固定完所有位
    res.append(''.join(num)) # 拼接 num 并添加至 res 尾部
    return
    for i in range(10): # 遍历 0 - 9
    num[x] = str(i) # 固定第 x 位为 i
    dfs(x + 1) # 开启固定第 x + 1 位

    num = ['0'] * n # 起始数字定义为 n 个 0 组成的字符列表
    res = [] # 数字字符串列表
    dfs(0) # 开启全排列递归
    return ','.join(res) # 拼接所有数字字符串,使用逗号隔开,并返回

    在此方法下,各数字字符串被逗号隔开,共同组成长字符串。返回的数字集字符串如下所示:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    输入:n = 1
    输出:"0,1,2,3,4,5,6,7,8,9"

    输入:n = 2
    输出:"00,01,02,...,10,11,12,...,97,98,99"

    输入:n = 3
    输出:"000,001,002,...,100,101,102,...,997,998,999"


观察可知,当前的生成方法仍有以下问题:

  1. 诸如 $00, 01, 02, \dots$ 应显示为 $0,1,2,\dots$,既应 删除高位多余的0
  2. 此方法从 0 开始生成,而题目要求 列表从 1 开始

以上两个问题的解决方法如下:

  1. 删除高位多余的0:

    • 字符串左边界定义:声明变量 $start$ 规定字符串的左边界,以保证添加的数字字符串 num[start:] 中无高位多余的0。例如当 $n=2$时,$1-9$时 $start=1, 10-99$时,start=0。

    • 左边界start变化规律:观察可知,当输出数字的所有位都是9时,则下个数字需要向更高位进 1 ,此时左边界start要减1(既高位多余的 0 减少一个)。例如当 $n=3$(数字范围 1-999)时,左边界 start需要减1的情况有:“009” 进位至 “010”, “099”进位至“100”。设数字各位中9的数量为 $nine$,所有位都为9的判断条件可用以下公式表示:
      $$
      n - start = nine
      $$

    • 统计$nine$的方法:固定第$x$位时,当 $i=9$则执行 $nine=nine+1$,并在回溯前恢复 $nine = nine-1$。

  2. 列表从1开始:

    • 在以上方法的基础上,添加数字字符串前判断其是否为 "0" ,若为 "0" 则直接跳过。

正确表示大数 ,以下代码的返回值为数字字符串集拼接而成的长字符串。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution:
def printNumbers(self,n: int) -> [int]:
def dfs(x):
if x == n:
s = ''.join(num[self.start:])
if s != '0':
res.append(s)
if n - self.start == self.nine:
self.start -= 1
return
for i in range(10):
if i == 9:
self.nine += 1
num[x] = str(i)
dfs(x+1)
self.nine -= 1
num,res = ['0'] *n, []
self.nine = 0
self.start = n-1
dfs(0)
return ','.join(res)


本题要求输出 int 类型数组。为 运行通过 ,可在添加数字字符串 $s$ 前,将其转化为 int 类型。代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Solution:
def printNumbers(self, n: int) -> [int]:
def dfs(x):
if x == n:
s = ''.join(num[self.start:])
if s != '0': res.append(int(s))
if n - self.start == self.nine: self.start -= 1
return
for i in range(10):
if i == 9: self.nine += 1
num[x] = str(i)
dfs(x + 1)
self.nine -= 1

num, res = ['0'] * n, []
self.nine = 0
self.start = n - 1
dfs(0)
return res

作者

bd160jbgm

发布于

2021-06-08

更新于

2021-06-08

许可协议