题目描述
解题思路
考虑以下两个问题:
- 最大的 n位数(记为 end)与位数 n之间的关系:例如,最大的 1位数是9,最大的 2位数是 99 ,最大的 3 位数是 999 ,以此类推,end=10n - 1
- 大数越界问题:当 n 较大时,end 会超出 int32 整型的取值范围,超出取值范围的数字无法正常存储。(但由于本题要求返回 int 类型数组,相当于默认所有数字都在 int32 整型取值范围内,因此这里不用考虑大数越界问题。感觉 LeetCode 这么设置失去了剑指offer上这题的本意。。。)
因此,通过一个 for循环生成结果列表即可,循环次数为 10n - 1。
Java
class Solution {
public int[] printNumbers(int n) {
int end = (int)Math.pow(10, n)-1; // Math.pow()返回值为double型
int[] res = new int [end];
for (int i=0; i<end; i++){
res[i] = i+1;
}
return res;
}
}
Python
class Solution(object):
def printNumbers(self, n):
"""
:type n: int
:rtype: List[int]
"""
# 先使用 range()方法生成可迭代对象,再使用 list()方法转化为列表并返回即可。
return list(range(1, 10**n))
时间复杂度 O(10n): 生成长度为 10n的列表需使用 O(10n)时间
空间复杂度 O(1):列表作为返回结果,不计入额外空间
《剑指offer》原题解法
实际上,本题的主要考点是大数越界情况下的打印。需要解决以下三个问题:
- 表示大数的变量类型:无论是 short / int / long … 等基本数据类型,数字的取值范围都是有限的。因此,大数的表示应该使用字符串 String 类型。 最直观的⽅法就是字符串⾥每个字符为 ’0’ 到 ’9’ 之间的某⼀个字符, 用来表示数字中的⼀位。
- 进位操作:当使用 int等数字的基本数据类型时,直接通过 +1 操作即可生成下一个数字,而此方法并无法应用至 String 类型上。因此,string类型表示的数字的进位操作会比较低效,例如从 “9999” 进位到 “10000” 需要从个位、十位、百位、千位依次循环判断,操作4次进位。
- 避开进位操作,转为数字的全排列:实际上,可以观察发现,如果在每个数字前面补 0 凑成 n位数的话,就会发现所有的十进制 n位数其实就是 n个 ’0’ ~ ’9’ 的全排列。也就是说,我们把数字的每一位都从 0 到 9 排列一遍,就可以得到所有的十进制数,从而避免了进位操作。
- 递归实现全排列:先固定高位,向低位递归,直到个位上的数字确定后,生成排列结果。例如,当 n=2(数字范围 1~99)时,固定十位为 0 ~ 9中的某个数,按顺序依次向低位递归,固定个位上的数字为 0 ~ 9中的某个数,并添加该数字字符串。
Java
本题要求输出 int 类型数组。为 运行通过 ,可在添加数字字符串 ss 前,将其转化为 int 类型。
class Solution {
int[] res;
int nine = 0; // 数字中 9 的个数
int count = 0;
int start; // 规定字符串的左边界,以保证添加的数字字符串 num[start:] 中无高位多余的 0 。
char[] num; // 定义表示大数的字符数组
char[] loop = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'}; // 0-9 数字对应的字符
public int[] printNumbers(int n) {
res = new int[(int)Math.pow(10, n) - 1];
num = new char[n]; // 申请长度为 n的字符数组表示一个 n位数
start = n - 1; // 从个位开始
dfs(0, n); // 递归实现全排列
return res;
}
void dfs(int x, int n) {
if (x == n) {
String s = String.valueOf(num).substring(start); // public String substring(int beginIndex) 返回子串
if(!s.equals("0")) // 每次添加数字字符串前判断其是否为 "0",若为 "0" 则直接跳过。因为要生成的是从1开始的数字
res[count++] = Integer.parseInt(s); // 每次添加字符串 s时,将其转为 int型
if(n - start == nine) start--; // 如果当前所有位上都为9,需要执行进位操作,左边界-1,即高位上多余的“0”减少一个
return;
}
for(char i : loop) {
if(i == '9') nine++; // 在固定某一位上的数字时,如果选到排列的数字是9,则 nine+1
num[x] = i;
dfs(x + 1, n);
}
nine--; // 递归生成当前数字后,在回溯之前恢复 nine = nine -1
}
}
另一种写法
public class Solution {
private List<Integer> list;
public int[] printNumbers(int n) {
list = new ArrayList<>();
dfs(n, 0, new StringBuilder()); // 从字符串的第0个位置开始递归,即从最高位开始递归
int[] res = new int[list.size()]; // 本题要求输出 int 类型数组
for (int i = 0; i < res.length; i++) {
res[i] = list.get(i);
}
return res;
}
private void dfs(int n, int i, StringBuilder sb) {
if (i == n) { // 当生成一个 n位数的字符串后,需要删除高位多余的0
while (sb.length() != 0 && sb.charAt(0) == '0') { // 判断最高位是否为 0
sb.deleteCharAt(0); // 循环删除索引为0 的字符,即最高位的字符
}
if (sb.length() != 0) {
list.add(Integer.valueOf(sb.toString())); // 将String转换成Integer
}
return;
}
// 递归实现全排列
for (int j = 0; j < 10; j++) {
sb.append(j); // 每次选择 0 ~ 9 中的某一个数字
dfs(n, i + 1, sb); // 递归对下一位上的数字进行排列
if (sb.length() != 0) { // 当递归返回时,即上一种生产的排列结果已经检查完毕
sb.deleteCharAt(sb.length() - 1); // 删除当前个位,等待下次循环更换另一个数
}
}
}
}
按照剑指offer本题原意,采用数字字符串集拼接成的长字符串正确表示的大数。
class Solution {
StringBuilder res;
int nine = 0, count = 0, start, n;
char[] num, loop = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
public String printNumbers(int n) {
this.n = n;
res = new StringBuilder();
num = new char[n];
start = n - 1;
dfs(0);
res.deleteCharAt(res.length() - 1);
return res.toString();
}
void dfs(int x) {
if(x == n) {
String s = String.valueOf(num).substring(start);
if(!s.equals("0")) res.append(s + ",");
if(n - start == nine) start--;
return;
}
for(char i : loop) {
if(i == '9') nine++;
num[x] = i;
dfs(x + 1);
}
nine--;
}
}
参考
https://leetcode-cn.com/problems/da-yin-cong-1dao-zui-da-de-nwei-shu-lcof/solution/mian-shi-ti-17-da-yin-cong-1-dao-zui-da-de-n-wei-2/