题目:输入数字 n,按顺序打印出从 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 一直到最大的 3 位数 999。
示例 1:
输入: n = 1 输出: [1,2,3,4,5,6,7,8,9]
说明:
- 用返回一个整数列表来代替打印
- n 为正整数
题目链接: 剑指 Offer 17. 打印从1到最大的n位数 - 力扣(LeetCode)
最简单的解法如下
func printNumbers(n int) []int {
num:=int(math.Pow10(n))
ans:=[]int{}
for i:=1;i<int(num);i++{
ans=append(ans,i)
}
return ans
}
本题主要考察大数问题,需要使用字符串存储大数。第一个解法是使用模拟加法,第二个解法是使用DFS对所有情况进行全排列。
解法一:模拟加法
可以先使用字符串存储高精度的数字,然后从 1 开始不断加一,直到最大的 n 位数。这个算法的时间复杂度是 O(10^n),空间复杂度是 O(n)。在 n 较大的情况下,可能会出现“超时”的错误。
func printNumbers(n int) []int {
if n <= 0 {
return nil
}
result := make([]int, 0)
num := make([]byte, n) // 定义一个长度为 n 的 byte 数组,用于存储高精度数字
for i := range num { // 将 num 初始化为 '0'
num[i] = '0'
}
for !increment(num) { // 不断增加 num
result = append(result, atoi(num)) // 将 num 转换为 int 类型,并添加到结果中
}
return result
}
// 将 byte 数组转换为 int
func atoi(num []byte) int {
sum := 0
for _, b := range num {
sum = sum*10 + int(b-'0')
}
return sum
}
// 将高精度数字加 1,返回是否溢出
func increment(num []byte) bool {
n := len(num) // 数组长度
takeOver := false // 是否需要进位
// 从最低位开始加 1
for i := n - 1; i >= 0; i-- {
var sum int
if i == n-1 { // 如果是最低位,值加 1
sum = int(num[i]-'0') + 1
} else { // 否则只加进位
sum = int(num[i]-'0')
}
if takeOver { // 如果需要进位,加上进位
sum++
takeOver = false
}
if sum >= 10 { // 如果需要进位,sum 减去 10,takeOver 设为 true
if i == 0 { // 如果最高位也需要进位,说明溢出了
return true
} else {
sum -= 10
takeOver = true
}
}
num[i] = byte(sum + '0') // 更新当前位的值
}
return false
}
increment 函数详解
increment 函数的执行过程就是将 num 数组表示的高精度数字加 1,并检查是否溢出。在这个函数维护一个布尔值 takeOver,表示是否需要进位。当加到最高位时,如果需要进位,则说明已经加到了最大值,溢出了。
以 num 数组为 { '1', '2', '9' } 的情况为例考察 increment 函数的执行过程。
首先循环处理 num 数组中的每一个元素 b。由于 b 的值为 9,因此需要进位将它变成 0,同时将 takeOver 设为 true。
接着进入下一个循环处理数组的第二个元素,它的值为 2。由于 takeOver 的值为 true,因此需要再加上 1,使之变成 3,同时将 takeOver 设为 false。
在第三次循环中处理数组的最高位,它的值为 1。由于 takeOver 的值为 false,而不需要进位。
最后将 num 数组更新为 { '1', '3', '0' } 并返回 false,表示计算未溢出。
因此,数组 { '1', '2', '9' } 被加 1 后变成了 { '1', '3', '0' }。
atoi 函数详解
atoi 函数是将 byte 数组转换成 int 类型的一个函数。
为了说明 atoi 函数的运行过程,举一个例子,假设 byte 数组为 num := []byte{'1', '2', '3'}。
定义一个变量 sum,初始值为 0。循环处理 num 数组中的每一个元素 b。
在第一次循环中,b 的值为 '1',可以将它转换成数字 1,于是 sum 的值变成了 1。
在第二次循环中,b 的值为 '2',可以将它转换成数字 2,于是 sum 的值变成了 12。
在第三次循环中,b 的值为 '3',可以将它转换成数字 3,于是 sum 的值变成了 123。
最后返回变量 sum 的值,也就是 123。因此,byte 数组 { '1', '2', '3' } 被转换为了数字 123。
-'0' 原理
在 atoi 函数中需要将 byte 数组中的每个元素转换成数字,以便使用它们进行计算。byte 类型的变量在计算机中实际上是用 8 个二进制位来表示的,而每个二进制可以表示 2 种状态(0 或 1),因此共有 256 种不同的状态(2^8 = 256)。在 ASCII 代码中,0 到 9 这些数字的代码分别是 48 到 57。因此,如果想将一个 byte 类型的变量 b 转换成它表示的数字,只需要将 b-'0' 的值作为该数字即可,因为 b 的 ASCII 代码减去 48 的 ASCII 代码的值就是它所表示的数字。
解法二:使用DFS全排列
使用DFS全排列的思路是:从高位到低位枚举所有的数字,每一位可以选择 0 到 9 中的任意一个数字,然后递归处理下一位。当处理完最低位后,将数字加入结果集中。
// 定义函数 printNumbers,它接受一个 int 类型的参数 n,返回一个 int 型的切片
func printNumbers(n int) []int {
// 如果 n 小于等于 0,直接返回 nil
if n <= 0 {
return nil
}
// 定义一个 int 型的切片 result,用于存储结果
result := make([]int, 0)
// 调用 dfs 函数进行深度优先搜索
dfs(&result, make([]byte, n), 0)
// 返回结果
return result[1:]
}
// 定义 dfs 函数,它接受三个参数,result 是一个 int 型切片指针,用于存储结果,
// num 是一个 byte 数组,用于存储当前正在处理的数字,
// index 表示当前处理的数字的位数
func dfs(result *[]int, num []byte, index int) {
// 如果处理完最低位,将数字加入结果集中,并返回
if index == len(num) {
*result = append(*result, atoi(num))
return
}
// 循环枚举当前位可以选择的数字
for i := byte('0'); i <= '9'; i++ {
num[index] = i // 将数字 i 存储在 num 的当前位置上
dfs(result, num, index+1) // 递归处理下一位
}
}
// 定义 atoi 函数,将 byte 数组转换为 int
func atoi(num []byte) int {
sum := 0 // 定义一个变量 sum,用于存储转换后的数字
for _, b := range num { // 循环处理 byte 数组中的每一个元素 b
sum = sum*10 + int(b-'0') // 将 b 转换为 int 类型,并加到 sum 中
}
return sum // 返回转换后的数字
}
举例解析
以 n = 2 的情况为例考察这个算法的执行过程。
首先调用 printNumbers(2)。
在 dfs 函数中创建了一个长度为 2 的 byte 数组,并将它传递给 dfs 函数。
在 dfs 函数中,由于 index = 0,开始处理第一位数字,枚举第一位数字的所有可能取值(0 到 9),将它们分别存储在 byte 数组中,并递归调用 dfs 函数处理下一位数字。
假设第一位数字取值为 0,那么继续处理第二位数字。同样枚举第二位数字所有可能的取值,将它们分别存储在 byte 数组中,并递归调用 dfs 函数处理下一位数字。
一直递归到处理完最低位后将转换后的数字存储在结果集中,回溯到上一层递归,枚举第二位数字的下一个可能取值,直到枚举完所有情况。
回溯到上一层递归,枚举第一位数字的下一个可能取值,重复上述过程,直到枚举完所有情况。
最终得到的结果集是 [1,2,3,4,5,6,7,8,9,10,...,99]。
总的来说,这个算法的执行过程就是对所有的 n 位数字进行深度优先搜索,并将符合条件的数字加入结果集中。由于搜索的时间复杂度为 O(10^n),因此在 n 很大的时候,这个算法的速度会变慢。