劍指offer上有一題:從1到N中1出現的次數,
比如N 爲12時,出現1的數字有1,10,11,12,那麼1總共出現5次。
思路:
使用遞歸思想,先考慮普通輸入N = 345,從1 -- 345可以分拆爲1 -- 45和46 -- 345(可以認爲是分分治思想)
分別求1 -- 45 和 46 -- 345中1出現的次數
- 對1 -- 45也可以採用上述方法進行拆分爲1 -- 5和6 -- 45,這就是遞歸的部分
- 對46--345這部分,我們需要知道有多個1:
1、首先關注345最高位3,因爲大於1,所以存在100--199 屬於46--345,也就是有10^(n - 1)個1(n爲345的位數)。
2、對於345,因爲first > 1(first爲最高位數值),這樣按照上一步來計算。當first == 1時,最高位爲1的出現次數從100--145,爲46次,也就是非最高位數值+1。
3、然後非最高位也可能存在1,如x1x,xx1,對x1x,最高位的x可以取1,2,3,個位的x可以取0--9,所以有3 * (10^1)種,同理xx1也有3*(10^1)種。所以非最高位存在1的總數爲 2 * 3 *(10^1)個,抽象爲(n - 1)*first *(10 ^(n - 2)),n爲345的位數。
4、求完46 -- 345中1的個數,然後求1--45中1的個數,這是就是遞歸了,1---45可以拆分爲1--5和6--45,然後6--45執行上述的計算,1--5繼續遞歸。
5、遞歸結束的條件是N的位數n == 1,這時候需要判斷這個傳入遞歸函數的N,if(N >= 1)return 1; if(N == 0)return 0;
源代碼:
public static int countDigitOne(int n) {
if(n < 0)
return 0; //異常處理
char[] ch = String.valueOf(n).toCharArray();
return numOfOne(ch, 0);
}
private static int numOfOne(char[] ch, int index) {
int first = ch[index] - '0'; //求取當下最高位的數值
if(ch.length - index == 1 && first == 0) // 遞歸條件判斷,輸入值是否爲1位且爲0
return 0;
if(ch.length - index == 1 && first >= 1) //輸入值是否爲1位且大於等於1
return 1;
int numOtherOne = 0;
if(first > 1) //求取最高位爲1的數字出現次數
numOtherOne = power(ch.length - index - 1);
if(first == 1) //求取最高位爲1的數字出現次數
numOtherOne = power1(ch, index);
int numLowOne = first * (ch.length - index - 1) * power(ch.length - index - 2); //求取非最高位爲1的數字出現次數
int numRecurive = numOfOne(ch, index + 1); //遞歸
return numOtherOne + numLowOne + numRecurive;
}
private static int power(int n) { //求取10^n
int m = 1;
for(int i = 0; i < n; i++)
m *= 10;
return m;
}
private static int power1(char[] ch, int index) { //求取子字符數組的數值
String s = String.valueOf(ch).substring(index + 1);
return Integer.valueOf(s) + 1;
}
複雜度分析:
- 時間複雜度:O(log(N))
每一次遞歸的時間複雜度爲O(1),一個數字N有log(N)位,所以遞歸深度爲log(N)
總的時間複雜度O(log(N))。
- 空間複雜度:需要創建一個字符數組來存放數字N的log(N)位。