标签(空格分隔): lintCode
题目: 统计数字
描述:
计算数字k在0到n中的出现的次数,k可能是0~9的一个值
样例
例如n=12,k=1,在 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12],我们发现1出现了5次 (1, 10, 11, 12)
解析
思路一:暴力求解法
从0遍历到n,将其中的每一个数的各个位分别于k进行对比,如果和k相等,则计数加一。
int digitCounts(int k, int n) {
int result = 0;
for (int i = k; i <= n; i++) {
int temp = i;
while (temp > 9){
int r = temp % 10;
if (r == k) {
result++;
}
temp /= 10;
}
if (temp == k) {
result++;
}
}
return result;
}
思路二:数学优化
我们看到这种方法的一个巨大的缺点是,当n越来越大时,我们需要分解的数,和步骤也越来越多,同时,我们发现我们分解了很大一部分得不到结果的数。这其实是一种浪费。
优化算法的原则:
- 尽可能的滤掉不符合条件的步骤。
- 尽量避免计算步骤随着n的增大,增长的过快。
基于这两个原则,如果能减少遍历次数,将会减少很大一部分运算量。最理想的情况下,如果我们指根据最后一个n能得出结果,这个就是最好的方式。分析整个题目,我们发现全部数字是处于一种递增的关系,也就是n其实是和总体之间是存在线性关联的,也就是说我们可以从n得出所有符合条件的数。
首先我们简化:
分析n = 10以内(不包括10)的数字,0,1,2,...,9,对任意一个数,总会出现一次,这里我们为了更加简化,我们先把0去掉,我们分析6.
分析100以内(去掉100),个位上的没10个就会出现一个6,分别是6,16,26,36,..,66,..,86,96;也就是个位上没个10个数就会出现一次,同时我们也看到十位上也出现了一次6就是60开始,我们发现60,61,62,63..,66,..,69,我们发现出现了10次,有人说66出现了两次,是不是该去掉,答案是不用,因为66本身其实就表示出现了两次6,
分析n = 300(去掉300)以内的数,个位数出现6的次数分别是6,16..,96,..,106,....,196..,206,..,296。每个10个出现一次,一共出现了300/10= 30次,十位上呢?分别是60,61..69,161..169,261..269;我们发现每隔100出现一次,出现3次,十位上每出现一次,机会增加10,共30次,则300以内6的个数为30+30 = 60次。
我们还发现一种情况:当n=900时,范围数值内出现了600-699的数值,这样会额外的增加100。我们需要进行修正。
依次类推,发现如果数字再打,则百位上出现一次6是每个1000出现一次,一次会增加100个。
于是我们可以这样理解:假设我们的n都是100,200,300,..1000,2000,9000,这样整百整千的数,我们是否可以写出代码呢?
显然,这个是很容易的。
我们需要一个条件,就是先知道这个数一共有多少位,然后根据位数来进行运算
int digitCounts(int k, int n) {
int result = 0; //结果
int base = 1;//最少是一位数
int tmp = n; //n的副本
int factor = 1;
//计算一共有多少位
while (tmp > 9){
base++;
tmp /= 10;
factor = factor * 10;
}
//计算最高位
int maxHigh = n / factor;
//从个位到十位,到百位...,累计出现的次数
//个位上出现一次就计数加1,十位上出现一次计数加10...
//计数倍数
int mulriple = 1;
for (int i = 1; i < base; i++) {
//计算在个位,十位,百位...出现的次数
//个位,每隔10个数,出现一次
int tFactor = 10 * mulriple;
//总共出现多少次
int time = factor/tFactor;
result = result + maxHigh * time * mulriple;
mulriple *= 10;
}
//修正结果,如果最高位大于k,则增加当前位进制大小的个数,
//如900之于6,存在一个600,则统计中增加了一个100,即factor
if (maxHigh > k) {
result += factor;
}
}
这样我们就可以计算n为整百整10的情况了,显然这样还够.还有一些其他的情况,如果n = 300,k = 3,我们没有把300计算进去,因此我们需要进去
int digitCounts(int k, int n) {
...//接着上面的
if (maxHigh == k) {
result = result +1;
}
}
这样我们就把整百的考虑完成了,下面我没考虑费整百的情况如123,353,..,这种情况。
我们的思路是这样,例如353为例,我们先考虑300以内的,然后加上300-353的。300以内的上面已经完成了,300-353的该如何做呢?当k > 最高位时,其实也就看看353-300 = 53 中有多少个满足条件的. k < 最高位时,我们上面已经考虑进去了,当 k = 最高位时,我们只需要将外的53在加上即可。因此我们的程序可以这样写。
int digitCounts(int k, int n) {
int result = 0; //结果
int base = 1;//最少是一位数
int tmp = n; //n的副本
int factor = 1;
//计算一共有多少位
while (tmp > 9){
base++;
tmp /= 10;
factor = factor * 10;
}
//计算最高位
int maxHigh = n / factor;
//从个位到十位,到百位...,累计出现的次数
//个位上出现一次就计数加1,十位上出现一次计数加10...
//计数倍数
int mulriple = 1;
for (int i = 1; i < base; i++) {
//计算在个位,十位,百位...出现的次数
//个位,每隔10个数,出现一次
int tFactor = 10 * mulriple;
//总共出现多少次
int time = factor/tFactor;
result = result + maxHigh * time * mulriple;
mulriple *= 10;
}
//修正结果,如果最高位大于k,则增加当前位进制大小的个数,
//如900之于6,存在一个600,则统计中增加了一个100,即factor
if (maxHigh > k) {
result += factor;
}
//计算剩余
n = n - maxHigh * factor;
if (maxHigh == k) {
result = result +n +1;
}
}
我们该如何计算剩余的部分计数呢?原理其实是一样的,我们只需要将这个数在进过一次过程就行了,因此我们可以写一个while循环,循环的结束条件是,只剩下个位。
int digitCounts(int k, int n) {
int result = 0; //结果
int base = 1;//最少是一位数
int tmp = n; //n的副本
int factor = 1;
//计算一共有多少位
while (tmp > 9){
base++;
tmp /= 10;
factor = factor * 10;
}
while (base > 1){
//计算最高位
int maxHigh = n / factor;
//从个位到十位,到百位...,累计出现的次数
//个位上出现一次就计数加1,十位上出现一次计数加10...
//计数倍数
int mulriple = 1;
for (int i = 1; i < base; i++) {
//计算在个位,十位,百位...出现的次数
//个位,每隔10个数,出现一次
int tFactor = 10 * mulriple;
//总共出现多少次
int time = factor/tFactor;
result = result + maxHigh * time * mulriple;
mulriple *= 10;
}
//修正结果,如果最高位大于k,则增加当前位进制大小的个数,
//如900之于6,存在一个600,则统计中增加了一个100,
//即factor
if (maxHigh > k) {
result += factor;
}
//计算剩余
n = n - maxHigh * factor;
if (maxHigh == k) {
result = result +n +1;
}
base--;
factor /= 10;
}
//将只有一位的情况也加上
if (n >= k) {
result++;
}
return result;
}
这样就完成了所有代码,但是这还远远不够,为什么这么说,因为我们上面所有的情况都没有考虑0的情况,我们发现,对n =1000,k = 3, 有这样的数字满足情况,3,30-39,40-49..300-399,当k = 0时,我们并不能找到,00-09,,000-099,这样的数,但是我们去将这种情况考虑进去了,因此我们需要去掉。
int digitCounts(int k, int n) {
int result = 0;
int base = 1;//最少是一位数
int tmp = n;
int factor = 1;
while (tmp > 9){//判断一共有多少位数,
base++;
tmp /= 10;
factor = factor * 10;
}
int tmpBase = base;
while (base > 1){
int maxHigh = n / factor;
//出现一次计数多少个
int mulriple = 1;
for (int i = 1; i < base; i++) {
//出现的次数,比如说,个位,没10个出现1次,出现了10次
//十位,每100出现了1次,
int tFactor = 10 * mulriple;
int time = factor / tFactor;
//当0出现在最高位时,不做计数,在计算剩余的时候却可以计数。10010,此时0010中00是起作用的
if (tmpBase == base && k == 0 && i > 1) {
time = time - 1;
}
result = result + maxHigh * time * mulriple;
mulriple *= 10;
}
if (maxHigh > k) {
if (k > 0) {
result += factor;
}
//计算剩余的时1100,此时计数100就需要将计数加上
else if (k == 0 && base < tmpBase) {
result += factor;
}
}
n = n - maxHigh * factor;
if (maxHigh == k) {
result = result + n +1;
}
base--;
factor /= 10;
}
if (n >= k) {
result++;
}
return result;
}