1からnまでの正の整数の中で1が現れる回数を統計します
3183 ワード
1からnまでの正の整数の中で1が現れる回数を統計します
1、最も直感的な考え方で、1からnまでの整数ごとに1が現れる回数を求め、加算すればよい.10進数ごとに1が現れる回数を求めるには、まずこの数の桁数が1であるかどうかを判断し、この数が10より大きい場合は、10で割った後に1であるかどうかを判断し、その整数が1を含む個数を求めるまで循環する.
コードは次のとおりです.
2、上記の考え方には非常に明らかな欠点があり、数字ごとに1がその数字に現れる回数を計算しなければならないので、時間複雑度はO(n)である.入力nが非常に大きい場合,大量の計算が必要であり,演算効率が低い.不要な計算を避けるためにいくつかの法則を見つけてみましょう.
例として少し大きい数字21345を用いて解析した.1から21345までのすべての数字を2つのセグメント、すなわち1〜1345と1346〜21345に分けた(セグメントの利点は、1345ビット21345が最上位を除去した結果、再帰演算が容易であるためである).
まず1346-21345中1の出現回数を見てみましょう.1の出現は2つの状況に分けられる:1つの状況は1が最高位(万位)に現れる.1から21345までの数字のうち、1は10000-1999という10000の数字の万位に現れ、合計10000(10^4)回現れた.もう1つのケースは、1が最上位を除く他のビットに現れることである.例では1346-21345で、この20000個の数字のうち後ろの4桁のうち1が2000回(2*10^3、うち2の1位の数値、10^3は数字の後ろの4桁の数字のうちの1桁が1であるため、残りの3桁の数字は0から9までの10個の数字を任意に選択することができ、配列の組み合わせから総回数が2*10^3であることがわかる).
1から1345までのすべての数字のうち1が現れる回数については,再帰的に求めることができる.これも私たちが1-21345を1-1345と1346-21345の2つのセグメントに分ける理由です.21345の最高位を取り除くと1345になるので、再帰的な考え方を採用するのに便利です.
ここまで分析すると、前述の例では、最上位は1より大きい数字であり、このとき最上位1が現れる回数は10^4(5桁に対して)であることに注意する必要があります.しかし、最高位が1だったら?たとえば12345と入力して、10000から12345までの数字の中で、1が万位に現れる回数は10^4回ではなく、2346回、つまり最上位の数字を除いた残りの数字に1を加えます.
前の分析に基づいて、以下のコードを書くことができます.リファレンスコードでは、プログラミングの便利さのために数字を文字列に変換しました.
1、最も直感的な考え方で、1からnまでの整数ごとに1が現れる回数を求め、加算すればよい.10進数ごとに1が現れる回数を求めるには、まずこの数の桁数が1であるかどうかを判断し、この数が10より大きい場合は、10で割った後に1であるかどうかを判断し、その整数が1を含む個数を求めるまで循環する.
コードは次のとおりです.
#include "stdafx.h"
#include "stdlib.h"
#include <iostream>
#include <string>
#include <stack>
#include <math.h>
using namespace std;
// 1
int NumberOf1(unsigned int n)
{
int number = 0;
while(n)
{
if(n % 10 == 1)
number ++;
n = n / 10;
}
return number;
}
// 1 n 1
int NumberOf1Between1AndN_Solution1(unsigned int n)
{
int number = 0;
for(unsigned int i = 1; i <= n; ++ i)
number += NumberOf1(i);
return number;
}
void main()
{
cout<<NumberOf1Between1AndN_Solution1(21345)<<endl; //18821
}
2、上記の考え方には非常に明らかな欠点があり、数字ごとに1がその数字に現れる回数を計算しなければならないので、時間複雑度はO(n)である.入力nが非常に大きい場合,大量の計算が必要であり,演算効率が低い.不要な計算を避けるためにいくつかの法則を見つけてみましょう.
例として少し大きい数字21345を用いて解析した.1から21345までのすべての数字を2つのセグメント、すなわち1〜1345と1346〜21345に分けた(セグメントの利点は、1345ビット21345が最上位を除去した結果、再帰演算が容易であるためである).
まず1346-21345中1の出現回数を見てみましょう.1の出現は2つの状況に分けられる:1つの状況は1が最高位(万位)に現れる.1から21345までの数字のうち、1は10000-1999という10000の数字の万位に現れ、合計10000(10^4)回現れた.もう1つのケースは、1が最上位を除く他のビットに現れることである.例では1346-21345で、この20000個の数字のうち後ろの4桁のうち1が2000回(2*10^3、うち2の1位の数値、10^3は数字の後ろの4桁の数字のうちの1桁が1であるため、残りの3桁の数字は0から9までの10個の数字を任意に選択することができ、配列の組み合わせから総回数が2*10^3であることがわかる).
1から1345までのすべての数字のうち1が現れる回数については,再帰的に求めることができる.これも私たちが1-21345を1-1345と1346-21345の2つのセグメントに分ける理由です.21345の最高位を取り除くと1345になるので、再帰的な考え方を採用するのに便利です.
ここまで分析すると、前述の例では、最上位は1より大きい数字であり、このとき最上位1が現れる回数は10^4(5桁に対して)であることに注意する必要があります.しかし、最高位が1だったら?たとえば12345と入力して、10000から12345までの数字の中で、1が万位に現れる回数は10^4回ではなく、2346回、つまり最上位の数字を除いた残りの数字に1を加えます.
前の分析に基づいて、以下のコードを書くことができます.リファレンスコードでは、プログラミングの便利さのために数字を文字列に変換しました.
#include "stdafx.h"
#include "stdlib.h"
#include <iostream>
#include <string>
#include <stack>
#include <math.h>
using namespace std;
//1 n 1
int CharNumberOf1(const char *strN)
{
int numFirstDigit=0,numOtherDigit=0,numRecursive=0;
int len=strlen(strN);
int firstDigit=*strN-'0';
if(len==1&&firstDigit==0)
return 0;
if(len==1&&firstDigit>0)
return 1;
// 1
if(firstDigit==1)
numFirstDigit=atoi(strN+1)+1;
else
numFirstDigit=pow(10,len-1);
// 1
numOtherDigit=firstDigit*(len-1)*pow(10,len-2);
// 1
numRecursive=CharNumberOf1(strN+1);
return numFirstDigit+numOtherDigit+numRecursive;
}
//1 n 1
int NumberOf1(int n)
{
char strN[50];
sprintf(strN,"%d",n);
return CharNumberOf1(strN);
}
void main()
{
cout<<NumberOf1(21345)<<endl; //18821
}