輸入數字 n,按順序列印出從 1 到最大的 n 位十進位制數。比如輸入 3,則列印出 1、2、3 一直到最大的 3 位數 999。
示例 1:
輸入: n = 1
輸出: [1,2,3,4,5,6,7,8,9]
說明:
- 用返回一個整數列表來代替列印
- n 為正整數
方法一:分治演算法/全排列
解題思路:
題目要求列印“從 1 至最大的 n 位數的列表”,因此需要考慮以下兩個問題:
- 最大的 n 位數(記為 end)和 位數 n 的關係: 例如最大的 1 位數是 9,最大的 2 位數是 99,最大的 3 位數是 999。則可退出公式:
end = 10^n - 1
- 大數越界問題: 當 n 較大時,end 會超出 int32 整型的取值範圍,超出取值範圍的數字無法正常儲存,但由於本題要求返回 int 型別陣列,相當於預設所有數字都在 int32 整型取值範圍內,因此不考慮大數越界問題。
因此,只需要定義區間 [1,10^n - 1] 和步長 1,通過 for 迴圈生成結果列表 res 並返回即可。
程式碼
class Solution {
public int[] printNumbers(int n) {
int end = (int)Math.pow(10, n) - 1;
int[] res = new int[end];
for(int i = 0; i < end; i++)
res[i] = i + 1;
return res;
}
}
複雜度分析
- 時間複雜度 O(10^n) :生成長度為 10^n 的列表需使用 O(10^n) 時間。
- 空間複雜度 O(1) :建立列表需使用 O(1) 大小的額外空間(列表作為返回結果,不計入額外空間)
其他知識點
大數列印解法:
實際上,本題的主要考點是大數越界情況下的列印,需要解決以下三個問題:
表示大數的變數型別:
- 無論是 short/int/long…… 任意變數型別,數字的取值範圍都是有限的。因此,大數的表示應用字串 String 型別。
生成數字的字串集:
- 使用 int 型別時,每輪可通過 +1 生成下個數字,而此方法無法應用至 String 型別。並且,String 型別的數字的進位操作效率較低,例如
9999
至10000
需要從個位到千位迴圈判斷,進位 4 次。 - 觀察可知,生成的列表實際上是 n 位 0 - 9 的全排列,因此可避開進位操作,通過遞迴生成數字的 String 列表。
- 使用 int 型別時,每輪可通過 +1 生成下個數字,而此方法無法應用至 String 型別。並且,String 型別的數字的進位操作效率較低,例如
遞迴生成全排列:
- 基於分支演算法的思想,先固定高位,向低位遞迴,當個位已被固定時,新增數字的字串,例如當 n = 2 時(數字範圍 1 - 99),固定十位為 0 - 9,按順序依次開啟遞迴,固定個位 0 - 9,終止遞迴併新增數字字串。
class Solution {
StringBuilder res;
int count = 0, 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]; // 定義長度為 n 的字元列表
dfs(0); // 開啟全排列遞迴
res.deleteCharAt(res.length() - 1); // 刪除最後多餘的逗號
return res.toString(); // 轉化為字串並返回
}
void dfs(int x) {
if(x == n) { // 終止條件:已固定完所有位
res.append(String.valueOf(num) + ","); // 拼接 num 並新增至 res 尾部,使用逗號隔開
return;
}
for(char i : loop) { // 遍歷 ‘0‘ - ’9‘
num[x] = i; // 固定第 x 位為 i
dfs(x + 1); // 開啟固定第 x + 1 位
}
}
}
在此方法下,各數字字串被逗號隔開,共同組成長字串,返回的數字集字串如下所示:
輸入: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"
觀察可知,當前的生成方法仍有以下問題:
- 諸如 00,01,02,…… 應顯示為 0,1,2,……,即應 刪除高位多餘的 0;
- 此方法從 0 開始生成,而題目要求 列表從 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。
- 字串左邊界定義: 宣告變數 start 規定字串的左邊界,以保證新增的數字字串
列表從 1 開始
- 在以上方法的基礎上,新增數字字串前判斷其是否為 “0” ,若為 “0” 則直接跳過。
複雜度分析:
- 時間複雜度 O(10^n) : 遞迴的生成的排列的數量為 10^n。
- 空間複雜度 O(n) : 字元列表 num 使用線性大小的額外空間。
程式碼:
為 正確表示大數 ,以下程式碼的返回值為數字字串集拼接而成的長字串。
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--;
}
}
本題要求輸出 int 型別陣列。為 執行通過 ,可在新增數字字串 ss 前,將其轉化為 int 型別。程式碼如下所示:
class Solution {
int[] res;
int nine = 0, count = 0, start, n;
char[] num, loop = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
public int[] printNumbers(int n) {
this.n = n;
res = new int[(int)Math.pow(10, n) - 1];
num = new char[n];
start = n - 1;
dfs(0);
return res;
}
void dfs(int x) {
if(x == n) {
String s = String.valueOf(num).substring(start);
if(!s.equals("0")) res[count++] = Integer.parseInt(s);
if(n - start == nine) start--;
return;
}
for(char i : loop) {
if(i == '9') nine++;
num[x] = i;
dfs(x + 1);
}
nine--;
}
}
題解來源
作者:jyd
連結:leetcode-cn.com/problems/da-yin-co...
來源:力扣(LeetCode)
來源:力扣(LeetCode)
連結:leetcode-cn.com/problems/da-yin-co...
本作品採用《CC 協議》,轉載必須註明作者和本文連結