原题内容:
给定一个整数 n,计算所有小于等于 n 的非负整数中数字 1 出现的个数。
示例:
输入: 13
输出: 6
解释: 数字 1 出现在以下数字中: 1, 10, 11, 12, 13 。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-digit-one
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
——————————————分割线-————————————————
我的题解
分析
这个题的题意就是计算出从1到n的这n个整数的10进制的表示里面的数字“1”出现的总次数,总的来说,有两种主要的思路,第一种是一个一个数的数,第二种是根据数字“1”在各个数位出现的规律,运用数学的方法巧算求得。
方法一:暴力法
思路:
很简单的思路,遍历1到n的每一个整数,不断地mod 10,并看余数的值,每一轮的余数是1时,则在计数上面加1
遗憾的是,此方法超时
此方法的java代码示例:
//方法一,暴力遍历,此方法超时
//2020.05.30 19:01
//36 / 40 个通过测试用例 状态:超出时间限制
//最后执行的输入:824883294
class Solution {
public int countDigitOne(int n) {
int ans=0;
for(int i=1;i<=n;i++){
ans+=jige1(i);
}return ans;
}
public int jige1(int m){
int one=0;
while(m>0){
if(m%10==1){
one++;
}
m/=10;
}return one;
}
}
方法二:数学方法找规律快速统计
思路:
想要计算1到n这n个数字里面数字“1”出现的次数,我们不妨依次统计所有数位上面的出现次数。直观的看,在个位上,数字1每10个数出现一次,在十位上,平均每连续100个数里面出现10次(比如说1-100),在百位上,平均每1000个连续数出现100次(比如说1051-2050)……依次类推!
那么我们在大的方向上需要对题目中传入的整数,即“int n”,进行一次次地除以10(取整或者取整加1,具体看其他条件)的操作,以表示,这个n里面包含了多少个数、多少个“连续10个数”、“多少个连续100个数”等等。比如说,n=2323的时候,我们知道在2323个数字里,在2320之前,出现了232个个位上的1,加上2321里面的那个个位上的1,一共是233个也就是2323/10+1个个位的数字1.进一步我们又可以得到,2323个数里面十位上面的1出现的次数出现了2323/100+1段,这个段数乘10就是十位出现1的次数,因为2323/100+1段数字当中,每一段里面有10个数字的十位数字是1……对于普通的数字,以此方法进行计算并求和
但是却有特殊情况,当某一位是0或者1的时候:
例如某一位遇到0,n=2320的时候,我们统计个位1出现的次数,直接2320/10就行,不用加1,因为2320个数里面不包含2321;
其次,某一位遇到1,比方说,n=2313,我们统计十位的1的个数,需要统计1~2310-1(共计231个)以及2310 ~2313的1的个数(具体原因是,这里的十位1不是以整段整段出现的,需要手动把最后一段不完整的个数求出。)
本思路的java代码:
/*方法二,需要考虑n的每一位是0?1?还是大于1的数字?
*作者@v7fgg
*执行用时 :0 ms, 在所有 Java 提交中击败了100.00%的用户
*内存消耗 :36.5 MB, 在所有 Java 提交中击败了33.33%的用户
*2020年5月31日 17:24
*/
class Solution {
public int countDigitOne(int n) {
int ans=0;
long pOf10=1;//注意不能int,会越界
int m=n;//复制一个n,因为后面几轮循环后还会涉及到n的原始值
while(m>0){
if(m%10==0){
ans+=m/10*pOf10;
}
else if(m%10>1){
ans+=(m/10+1)*pOf10;
}
else{//此时m%10==1需要单独考虑
ans+=m/10*pOf10+(n%(pOf10*10))-pOf10+1;
}
pOf10*=10;
m/=10;
}
return ans;
}
}
本思路精简代码:
另外在判断除以10结果是否加1的那里,我对代码里面的情况进行了合并,运算优化了一些,代码得到了一些精简,上述代码可改为:
/*方法二代码的精简版
*作者@v7fgg
*执行用时 :0 ms, 在所有 Java 提交中击败了100.00%的用户
*内存消耗 :36.3 MB, 在所有 Java 提交中击败了33.33%的用户
*2020年5月31日 17:33
*/
class Solution {
public int countDigitOne(int n) {
int ans=0;
long pOf10=1;//注意不能int,会越界
int m=n;//复制一个n,因为后面几轮循环后还会涉及到n的原始值
while(m>0){
ans+=m%10!=1?(m+9)/10*pOf10:m/10*pOf10+(n%(pOf10*10))-pOf10+1;
pOf10*=10;
m/=10;
}
return ans;
}
}