#資料結構與演算法學習筆記#劍指Offer29:整數中1出現的次數 + 分段思想/按位考慮 + 測試用例(Java、C/C++)
2018.10.5
感受到開學之後工作和課業的雙重壓力,加上近段時間自己出了點小事故,因此斷更了許久。沒事,繼續。
這道題有兩種複雜度為的演算法。
方法1:遞迴(分段思想)。
所有數字出現1的個數 = 每一段數字中出現1的個數之和
1. 對於輸出的數字n,其最高位為x,將其分成1-i、i+1-n兩段。其中,i為n除以x的餘數,i-n的數字數目為x倍數(例如n=21345,x為10000,則將n分為1-1345,1346-21345)。
2. 後半段中最高位上取1的情況分為兩種:若n最高位數字>1,則最高位出現1的次數為x次;若n最高位數字=1,這最高位出現1的次數為i+1次。(例如後半段為1346-21345,則最高位萬位上1出現的次數為10000次;若後半段為1346-11345,則最高位萬位上1出現的次數為1346次)。
3. 後半段中其他數位上出現1的情況:將後半段等分為m份,每份數字數目為x(例如後半段為1346-21345,則將其分為1346-11345、11346-21345,每份數字數目為10000個)。除去最高位後,剩下的數字有y位。在任一一位上置1,其餘y-1位上可以任取0-9,則根據排列組合,後半段其餘數位總共出現1的次數為.(後半段分為01346-11345與11346-21345,去掉萬位後,剩餘數字有4位,根據公式,後半段數位總共出現1的次數為)
4.計算後半段,遞迴處理前半段。
方法2:歸納(按位考慮)。
所有數字出現1的個數 = 每一位數位上出現1的個數之和
從低到高遍歷數字的每一數位loc,對於每一位loc,其當前位數字為now(1位),高位數字high(多位,若不存在則為0),低位數字low(多位,不存在則為0)。對於每一位loc上可能取1的情況:
1. now==0(次數=high*loc)
例如21045,對於百位來說,loc=100,high=21,1出現的情況有00100-00199(100次)、01100-01199(100次)……19100-19199(100次)、20100-20199(100次),一共2100次。
2. now==1(次數=high*loc + (low+1))
例如21145,low=145,對於百位來說,除去上述的2100次,還有21100-21145,一共145+1=146次。總計2246次
3. now>=2(次數=(high+1)*loc)
例如21545,對於百位來說,除去上述的2100次,還有21100-21199,一共100次。總計2200次。
方法二還可以更一般地歸納為一條公式,具體見程式碼具體實現。
兩種方法可拓展到整數中數字x出現的次數求解
題目描述
求出1~13的整數中1出現的次數,並算出100~1300的整數中1出現的次數?為此他特別數了一下1~13中包含1的數字有1、10、11、12、13因此共出現6次,但是對於後面問題他就沒轍了。ACMer希望你們幫幫他,並把問題更加普遍化,可以很快的求出任意非負整數區間中1出現的次數(從1 到 n 中1出現的次數)。
Java實現(按位考慮):
/**
*
* @author ChopinXBP
* 求從1到n中1在數位上出現的次數
*
*/
public class NumberOf1Between1AndN_30 {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println(NumberOf1Between1AndN_Solution1(12023));
System.out.println(NumberOf1Between1AndN_Solution2(12023));
System.out.println(NumberOf1Between1AndN_Solution3(12023));
}
//歸納:按位考慮
public static int NumberOf1Between1AndN_Solution1(int n) {
if(n <= 0) return 0;
int result = 0;
int loc = 1; //當前位數
int high = 0; //高位數字(多位)
int now = 0; //當前位數字(1位)
int low = 0; //低位數字(多位)
while(n / loc > 0){
high = n / (loc * 10);
now = n / loc % 10;
low = n % loc;
if(now == 0){
result += high * loc;
}
else if(now == 1){
result += high * loc + low + 1;
}
else if(now >= 1){
result += (high + 1) * loc;
}
loc *= 10;
}
return result;
}
//簡潔寫法1,在C++下可歸納為一條公式
//當前位now=0/1時,+8對高位high無影響;當前位now>=2時,+8會產生進位,效果等同於high+1
public static int NumberOf1Between1AndN_Solution2(int n) {
int result = 0;
for (long loc = 1; loc <= n; loc *= 10) {
if(n / loc % 10 == 1){
result += (n / loc + 8) / 10 * loc + (n % loc + 1);
}else{
result += (n / loc + 8) / 10 * loc;
}
}
return result;
}
//簡潔寫法2,歸納為一條公式
//判斷去掉高位後的餘數,對於後半式子,若當前位小於1,輸出0;若當前位等於1,輸出low低位數字+1;若當前位大於1,輸出一個loc當前位,等效於(high+1)*loc
public static int NumberOf1Between1AndN_Solution3(int n) {
if (n <= 0)
return 0;
int result = 0;
for (long loc = 1; loc <= n; loc *= 10) {
long high = n / (loc * 10); //高位數字
long rest = n % (loc * 10); //去掉高位數字後的餘數
result += high * loc + Math.min(Math.max(rest - loc + 1, 0), loc);
}
return result;
}
}
C++實現(分段思想):
int NumberOf1(const char* strN);
int PowerBase10(unsigned int n);
int NumberOf1Between1AndN_Solution2(int n)
{
if(n <= 0)
return 0;
char strN[50];
sprintf(strN, "%d", n);
return NumberOf1(strN);
}
int NumberOf1(const char* strN)
{
if(!strN || *strN < '0' || *strN > '9' || *strN == '\0')
return 0;
int first = *strN - '0';
unsigned int length = static_cast<unsigned int>(strlen(strN));
if(length == 1 && first == 0)
return 0;
if(length == 1 && first > 0)
return 1;
// 假設strN是"21345"
// numFirstDigit是數字10000-19999的第一個位中1的數目
int numFirstDigit = 0;
if(first > 1)
numFirstDigit = PowerBase10(length - 1);
else if(first == 1)
numFirstDigit = atoi(strN + 1) + 1;
// numOtherDigits是01346-21345除了第一位之外的數位中1的數目
int numOtherDigits = first * (length - 1) * PowerBase10(length - 2);
// numRecursive是1-1345中1的數目
int numRecursive = NumberOf1(strN + 1);
return numFirstDigit + numOtherDigits + numRecursive;
}
int PowerBase10(unsigned int n)
{
int result = 1;
for(unsigned int i = 0; i < n; ++ i)
result *= 10;
return result;
}
C++實現(按位考慮):
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n){
int ones = 0;
for (long long m = 1; m <= n; m *= 10)
ones += (n/m + 8) / 10 * m + (n/m % 10 == 1) * (n%m + 1);
return ones;
}
};
#Coding一小時,Copying一秒鐘。留個言點個讚唄,謝謝你#