题目描述:
输入一个整数n,求从1到n个整数的十进制表示中1出现的次数。例如输入12,从1到12这些整数中包含1的数字有1,10,11,和12。一共出现了5次。
分析思路1:不考虑时间效率的解法,靠它想拿offer有点难
如果在面试的时候碰到这个问题,应聘者大多能想到最直观的方法,也就是累加1到n中每个整数1出现的次数。我们可疑每次通过对10求余数判断整数的个位数字是不是1。如果这个数字大于10,除以10之后再判断个位数字是不是1。
代码如下:
/**
* 1到n整数中1出现的次数
*/
public class OccurrenceNum {
public int times(int n){
if(n < 1){
return 0;
}
int count = 0;
for(int i = 1; i <= n; i++){
count += core(i);
}
return count;
}
public int core(int num){
int times = 0;
while(num != 0){
if(num%10 == 1){
times++;
}
num = num / 10;
}
return times;
}
public static void main(String[] args) {
OccurrenceNum test = new OccurrenceNum();
System.out.println(test.times(120));
}
}
从上述思路中,我们对每个数字都要做出发和求余运算以求出该数字中1出现的次数。如果输入数字n,n有O(logn)位,我们需要判断每一位是不是1,那么它的时间复杂度是O(n*logn)。当输入n非常大的时候,需要大量的计算,运算效率不高。面试官不会满意这种算法
分析思路2:从数字规律着手明显提高时间效率的解法,能让面试官耳目一新
如果希望不用计算每个数字的1的个数,那就只能寻找1在数字中出现的规律了。为了找到规律,我们不妨用一个稍微大一点的数字比如21345作为例子来分析。我们把从1到21345的所有数字分为两段,一段是从1到1345,另一段是从1346到21345.划分的原因是便于利用递归的思路,因为21345去掉最高位就是1345.我们先看1346到21345中1出现的次数。1的出现分为两种情况:1出现在最高位和1出现在其他位。1346到21345,1出现在10000-19999这10000个数字的最高位中,一共出现了10000个,即10^最高位。我们可以发现一般情况:如果是1346到11345,1出现在10000-11345的最高位中,一共出现2346次,也就是除去最高数字之后剩下的数字+1,当万位大于1时,1出现在最高位的次数是10^最高位。
接下来分析1出现在除最高位之外的其他四位数中的情况。1346-21345这20000个数字中后4位出现的次数,分成两段,1346-11345和11346-21345,每一段剩下的4位数字中,选择其中一位是1,其余三位可以在0-9这10个数字中任意选择,根据排列组合原则,总共出现次数2*4*10^3=8000次。我们可以发现一般情况:如果是1346-n1345那么,可以划分为n段,即1出现在除最高位之外的其他四位数的次数是n*4*1000;
代码如下:
/**
* 1到n整数中1出现的次数(使用数学规律)
*
*/
public class OccurrenceNum2 {
public int numberOf1Between1AndN(int n){
if(n<=0)
return 0;
String strN = String.valueOf(n);
return numberOf1(strN);
}
public int numberOf1(String strN){
if(strN.length() == 1){
return 1;
}
if(new Integer(strN) <= 0){
return 0;
}
int times = 0;
Integer m = new Integer(strN);
Integer n = new Integer(strN.substring(1))+1;
//计算最高位1的个数
if(new Integer(strN.substring(0, 1)) > 1){
times += Math.pow(10, strN.length()-1);
}else{
times += new Integer(strN.substring(1))+1;
}
//计算其他位1的个数
times += new Integer(strN.substring(0,1)) * (strN.length()-1) * Math.pow(10, strN.length()-2);
return times + numberOf1(strN.substring(1));
}
public static void main(String[] args) {
OccurrenceNum2 test = new OccurrenceNum2();
System.out.println(test.numberOf1Between1AndN(234565));
}
}