好长时间没有练算法了,笔试题1做,发现非常费劲,所以近日来找来《编程之美》1书来看看练练。为了鼓励自己多练,楼楼可能会出个专栏甚么的,感兴趣的同学我们可以1起抱团,楼楼也会保证每天都会更新。那今天呢,就是《编程之美》的第1题了,原题叫做“1”的数目,楼楼会把这道题还有相干的1些题都会记录下来,下面要开始了哦,Are you ready?
我相信如果是我们正在处于笔试或面试确当场,暴力穷举肯定是我们的第1个想法。1个1个算,算出1中出现“1”的个数,再算出2中出现“1”的个数,顺次类推,直到N中“1”的个数,然后相加得出最后的结论。我们看代码:
#include <iostream>
using namespace std;
int getOneShowTimes(unsigned int n) ;
int getOneShowSumTimes(unsigned int N) ;
int main()
{
unsigned int N = 123;
cout << "1出现的次数为:" << getOneShowSumTimes(N) << endl;
system("pause");
}
int getOneShowSumTimes(unsigned int N)
{
unsigned int count = 0;
for (unsigned int i = 0; i <= N; i++)
{
count += getOneShowTimes(i);
}
return count;
}
int getOneShowTimes(unsigned int n)
{
unsigned count = 0;
while(0 != n)
{
count += (n % 10) == 1 ? 1 : 0;
n /= 10;
}
return count;
}我们分析1下复杂度,外层循环要循环N次,内存循环要循环
下面我们想一想,有无更简单的办法呢?比如对1个3位数123而言,“1”只能在个位出现,或10位出现或千位出现。如果是依照这个原理来统计的,那我们可以完全将外层循环下降到
如123,那末
| 个位出现1 | 10位出现1 | 百位出现1 |
|---|---|---|
| 1 | 10 | 100 |
| 11 | 11 | 101 |
| 21 | 12 | 102 |
| 31 | 13 | 103 |
| 41 | 14 | 104 |
| 51 | 15 | … |
| 61 | 16 | |
| 71 | 17 | |
| 81 | 18 | |
| 91 | 19 | |
| 101 | 110 | |
| 111 | … | |
| 121 | 119 | 123 |
| 总计13次 | 总计20次 | 总计24次 |
| 料想公式 |
料想公式 |
料想公式 |
但是在这里有我们有几个特殊的情况需要特别斟酌,如相应的位数为0怎样办?比如51和50结果是完全不1样的。还有相应的位数为1怎样办?12和22的结果也是不1样的。我下面把结果罗列出来,大家也能够试着推导1下。
| 分情况 | 个位出现1 | 10位出现1 | 百位出现1 |
|---|---|---|---|
| bit = 0 | |||
| bit = 1 | |||
| bit > 1 |
总结1下,假定每位对应的权值为quan,如个位quan = 1,10位quan = 10,那末总的公式为
| bit=0 | bit=1 | bit>1 |
|---|---|---|
下面看代码:
package net.mindview.util;
public class MyThread {
public static void main(String[] args) {
int N = 123;
System.out.println("总共出现" + getOneShowTimes(N) + "次");
}
public static int getOneShowTimes(int N) {
int numPerBit; //存储每位的数目
int sumTimes = 0; //存储最后的结果
int quan = 1; //每位的权值,各位为1,10位为10,顺次类推
int tempN = N;
if (0 == N) {
return 0;
}
while(0 != tempN) {
numPerBit = tempN % 10;
sumTimes += getOneShowTimesPerBit(N, numPerBit, quan);
tempN /= 10;
quan *= 10;
}
return sumTimes;
}
public static int getOneShowTimesPerBit(int N, int numPerBit, int quan) {
if (0 == numPerBit) {
return N / (quan * 10) * quan;
} else if (1 == numPerBit) {
return (N / (quan * 10)) * quan + N % quan + 1;
} else {
return (N / (quan * 10) + 1 ) * quan;
}
}
}非常不好意思,这里的代码是Java的,由于原来就写好了,我就懒得再写成c++的了,请见谅哈!复杂度为
题目1是10进制,题目2是2进制。注意这里的区分。那我们一样写写看,看能不能找出甚么规律
假定N=3,那末我们看每位出现“1”的次数之和都是相等的,都是2次。那末结果就是2 * 2=4总共4次。其次,假定N=7,我们又惊奇地发现,所有小于7的数中每位出现“1”的次数之和也是相等的,每位出现“1”的次数之和为4,那末结果就是3 * 4 = 12次。那我们可以料想,当N=15时,总数为4 * 8 = 32次。这是非常理想的情况,那当N = 13呢?很easy,N=13,我们就先算N = 7。还剩下6个数,视察1下我们可以发现,剩下的6个数中“1”出现的次数=最小的6个数“1”出现的次数+6。那末我们就把问题降下来了,本来是要求左侧6个数中1出现的个数,转变成了求右侧6个数中1出现的个数。
| 本来要求的“1”出现的个数 | 简化以后要求的“1”出现的个数 |
|---|---|
| 1000 | 0000 |
| 1001 | 0001 |
| 1010 | 0010 |
| 1011 | 0011 |
| 1100 | 0100 |
| 1101 | 0101 |
那末剩下的6个数,又可以重复前面的步骤,先求N=3。
总结1下,假定不大于N的最小的2的次方数为
如果您觉得本网站对您的学习有所帮助,可以手机扫描二维码进行捐赠