输入任意一个数,得到1到这个数之间的1的个数

好久没写博客了,忙着面试,现正学习各种算法,今天碰到一个题。就是题目所写的那样,输入任意一个数,得到1到这个数之间的1的个数。比如这个数是12,那么函数f(12)返回的是5(因为中间有5个1)。

另外说明一下,由于个人水平有限,算法的效率有待提高,欢迎高手指点。

function one_ques($num){ $count=0; $str=""; if($num!=""||$num>=0){ for($i=1;$i<=$num;$i++){ $str.=$i; } echo $str; for($j=0;$j<=strlen($str);$j++){ $str1=substr($str,$j,1); if($str1==1){ $count++; } } return "从1到".$num."有".$count."个1"; }else{ return false; } } $num=1234; var_dump(one_ques($num));

当$num=100000000时,代码大概执行了50秒,并且是在修改了30秒运行超时的错误和内存溢出的错误之后,说明代码的问题很大,应该优化php代码。

仔细分析这个问题,给定了N,似乎就可以通过分析"小于N的数在每一位上可能出现 1 的次数"之和来得到这个结果让我们 来分析一下对于一个特定的  N,如何得到一个规律来分析在每一位上所有出现 1 的可能性,并求和得到最后的 fN)。

先从一些简单的情况开始观察,看看能不能总结出什么规律。

先看 1 位数的情况。

如果 N  =  3,那么从 1 3 的所有数字:123,只有个位数字上可能出现 1,而且只出现 1 次,进一步可以发现如果 N 是个位数,如果 N>=1,那么fN)都等于 1,如果 N=0,则 fN)为 0

再看 2 位数的情况。

如果 N=13,那么从 1 13 的所有数字:123456 78910111213,个位和十位的数字上都可能有 1,我 们可以将它们分开来考虑,个位出现 1 的次数有两次:1 11,十位出现 1 的次数有 4 次:101112 13,所以 fN=2+4=6。要注意的是 11 这个数字在十位和个位都出现了 1,但是 11 恰好在个位为 1 和十位为 1 中被计算了两次,所以不用特殊处理,是对的。再考虑 N=23 的情况,它和 N=13 有点不同,十位出现 1 的次数为 10 次,从 10 19,个位出现 1 的次数为 111 21,所以fN=3+10=13。通过对两位数进行分析,我们发现,个位数出现 1 的次数不仅和个位数字有关,还和十位数有关:如果 N 的个位数大于等于 1,则个位出现 1 的次数为十位数的数字加 1;如果N 的个位数为 0,则个位出现 1 的次数等于十位数的数字。而十位数上出现  1 的次数不仅和十位数有关,还和个位数有关:如果十位数字等于 1,则十位数上出现 1 的次数为个位数的数字加 1;如果十位数大于 1,则十位数上出现 1 的次数为 10

f(13) = 个位出现1的个数 + 十位出现1的个数 = 2 + 4 = 6

f(23) = 个位出现1的个数 + 十位出现1的个数 = 3 + 10 = 13

f(33) = 个位出现1的个数 + 十位出现1的个数 = 4 + 10 = 14

...

f(93) = 个位出现1的个数 + 十位出现1的个数 = 10 + 10 = 20

接着分析 3 位数.

如果 N = 123

个位出现 1 的个数为 131, 11, 21, ¼, 91, 101, 111, 121

十位出现 1 的个数为 201019, 110119

百位出现 1 的个数为 24100123

f23=  个位出现 1 的个数  +  十位出现 1 的个数  +  百位出现 1 的次数  = 13 + 20 + 24 = 57

同理我们可以再分析 4 位数、5 位数。读者朋友们可以写一写, 总结一下各种情况有什么不同。

根据上面的一些尝试,下面我们推导出一般情况下,从 N 得到 fN)的计算方法:

假设 N=abcde,这里 abcde 分别是十进制数 N 的各个数位上的数字.如果要计算百位上出现  1 的次数,它将会受到三个因素的影响:百位上的数字,百位以下(低位)的数字,百位(更高位)以上的数字。

如果百位上的数字为 0,则可以知道,百位上可能出现 1 的次数由更高位决定,比如 12 013,则可以知道百位出现 1 的情况可能10019911001 19921002199¼11100~11199,一共有 1200 个。也就是由更高位数字(12)决定,并且等于更高位数字(12×当前位数(100).

如果百位上的数字为 1,则可以知道,百位上可能出现 1 的次数不仅受更高位影响,还受低位影响,也就是由更高位和低位共同决定。例如对于 12113,受更高位影响,百位出现 1 的情况是 1001991100119921002199¼11100~11199,一共 1200个,和上面第一种情况一样,等于更高位数字(12×当前位数(100但是它还受低位影响,百位出现 1 的情况是 12 10012 113,一共114 个,等于低位数字(123+1如果百位上数字大于  1(即为  2~9),则百位上可能出现1的次数也仅由更高位决定,比如 12 213,则百位出现 1 的可能性为:1001991 1001 1992 1002 199¼11 10011 19912 10012 199,一共有 1 300 个,并且等于更高位数字+112+1 ×当前位数(100).

通过上面的归纳和总结,我们可以写出如下的更高效算法来计算 fN):

function Sum1s($n) { $iCount = 0; $iFactor = 1; $iLowerNum = 0; $iCurrNum = 0; $iHigherNum = 0; while($n/$iFactor != 0) { $iLowerNum = $n - ($n / $iFactor) * $iFactor; $iCurrNum = ($n / $iFactor) % 10; $iHigherNum = $n / ($iFactor * 10); switch($iCurrNum) { case 0: $iCount += $iHigherNum * $iFactor; break; case 1: $iCount += $iHigherNum * $iFactor+$iLowerNum+1; break; default: $iCount += ($iHigherNum + 1) * $iFactor; break; } $iFactor *= 10; } return intval($iCount); } $n=134; var_dump(Sum1s($n));

这个函数还有点瑕疵,返回的结果不对貌似,希望高手指点一下。

 

 

 

 

 

你可能感兴趣的:(输入任意一个数,得到1到这个数之间的1的个数)