题目描述
求出1~13的整数中1出现的次数,并算出100~1300的整数中1出现的次数?为此他特别数了一下1~13中包含1的数字有1、10、11、12、13因此共出现6次,但是对于后面问题他就没辙了。ACMer希望你们帮帮他,并把问题更加普遍化,可以很快的求出任意非负整数区间中1出现的次数(从1 到 n 中1出现的次数)。
解题思路:
思路1:暴力,原本以为过不了,竟然过了 。。。
代码如下:(都不好意思粘贴代码了 7^7
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n)
{
if (n == 0){
return 0;
}
int ans = 1;
int tol;
for (int i = 2; i <= n; i++){
tol = i;
while (tol != 0){
if (tol%10 == 1){
ans++;
}
tol = tol / 10;
}
}
return ans;
}
};
思路2:咳咳,接下来我们用温柔的解法来解释这道题目,其实这道题目就是总结找规律的题目,我之前想到的是分别统计每个位数(个位、十位、百位 。。)上“1”的个数,然后求和即可,奈何当时思路不清晰,没有总结出一个统一的规律,之后稍加梳理,便可以总结出相应的规律。
首先我们来看个位,
对于个位来说,“1”的出现仅仅是1,11上的个位,21上的个位等等,可以看出1的出现周期是每10个出现一次,因此我们可以通过个位以上的高位数字得到个位上1的个数,同时要注意特殊情况!当数字是20的时候,只出现了2个1,但是十位有进位,因此当个位是0的时候我们需要特殊处理。综上,我们可以得到个位上出现1的个数为:
n/10 * 1+(n%10!=0 ? 1 : 0)
我们接下来看十位数出现1的个数,十位出现1仅仅是在10~19,110~119。。。这里把10~19定义为一个完整的周期,因此我们可以得到,当十位数字大于1时,就会有完整的周期出现,这时十位数字出现1的个数仅仅是由十位的高位数字所就决定;当十位数字等于1时,这时存在一个不完整的周期,这个不完整周期中1的个数由十位的低位数字决定;当十位数字等于0时,缺少一个完整周期。综上,可以得到十位数字出现1的个数为,
- 设k = n % 100,即为不完整周期的数字
- 归纳式为:(n / 100) * 10 + (if(k > 19) 10 else if(k < 10) 0 else k - 10 + 1)
下面我们继续来看百位,有了十位做铺垫,我们很容易就可以推导出百位上1出现的个数,即当百位数字大于1,有完整的整周期,由高位数字决定整周期的数目;当百位数字等于1,存在不完整周期,由低位数字决定不完整周期中1的个数;当百位数字等于0,缺少一个完整周期,综上,可以得到百位数字上1出现的个数,
- 设k = n % 1000
- 归纳式为:(n / 1000) * 100 + (if(k >199) 100 else if(k < 100) 0 else k - 100 + 1)
这时我们已经发现了规律,把个位的计算公式也写成通式的形式,即,
- k = n % 10
- 个位数上1的个数为:n / 10 * 1 + (if(k > 1) 1 else if(k < 1) 0 else k - 1 + 1)
综上,我们可以得到这个题的答案是:
- k = n % (i * 10)
- count(i) = (n / (i * 10)) * i + (if(k > i * 2 - 1) i else if(k < i) 0 else k - i + 1)
- sum1 = sum(count(i)),i = Math.pow(10, j), 0<=j<=log10(n)
看牛客网的大神觉得太多的if-else让代码变得复杂,所以想把判断语句改为更简单的形式,即当不完整周期出现的时候选择不完整周期,不完整周期不存在的时候(为负数或i)选择0 或 i,具体表达式如下:
min(max((n mod (i*10))−i+1,0),i)
按照这个思路写的代码如下:
class Solution {
public:
int NumberOf1Between1AndN_Solution(int n)
{
if (n == 0){
return 0;
}
int ans = 0;
for (int i = 1; i <= n; i = i * 10){
long dri = i * 10;
int a = n%dri - i + 1;
if (a > 0){
if (a > i){
ans += i;
}
else{
ans +=a;
}
}
else {
if (i > 0){
ans += 0;
}
else {
ans += i;
}
}
ans += n/dri*i;
}
return ans;
}
};
思路3:研究思路2的推导,我们发现不论是否存在不完整周期,1的个数都由当前位数的高位决定整体数目,低位决定不完整周期的1的个数,因此对n进行分割,分为两部分,高位n/i,低位n%i
当i表示百位,且百位对应的数>=2,如n=31456,i=100,则a=314,b=56,此时百位为1的次数有a/10+1=32(最高两位0~31),每一次都包含100个连续的点,即共有(a%10+1)*100个点的百位为1
当i表示百位,且百位对应的数为1,如n=31156,i=100,则a=311,b=56,此时百位对应的就是1,则共有a%10(最高两位0-30)次是包含100个连续点,当最高两位为31(即a=311),本次只对应局部点00~56,共b+1次,所有点加起来共有(a%10*100)+(b+1),这些点百位对应为1
当i表示百位,且百位对应的数为0,如n=31056,i=100,则a=310,b=56,此时百位为1的次数有a/10=31(最高两位0~30)
综合以上三种情况,当百位对应0或>=2时,有(a+8)/10次包含所有100个点,还有当百位为1(a%10==1),需要增加局部点b+1
这里补8非常巧妙,是因为当百位为0,则a/10==(a+8)/10,当百位>=2,补8会产生进位位,效果等同于(a/10+1)。
代码如下:
链接:https://www.nowcoder.com/questionTerminal/bd7f978302044eee894445e244c7eee6?f=discussion
来源:牛客网
int count=0;
long long i=1;
for(i=1;i<=n;i*=10)
{
//i表示当前分析的是哪一个数位
int a = n/i,b = n%i;
count=count+(a+8)/10*i+(a%10==1)*(b+1);
}
return count;
}
};
其实通过思路2和思路3的分析,我们不仅可以得到1的个数,也可以得到数字1~9任意一个数字出现的次数,同时数字0情况比较特殊,需要单独进行分析。
参考资料:https://www.nowcoder.com/questionTerminal/bd7f978302044eee894445e244c7eee6?f=discussion 牛客网
本文深入解析了求解1~n中数字1出现次数的问题,提供了三种解题思路,包括暴力解法、按位统计优化解法及高效算法。通过观察和归纳,总结出适用于任意非负整数区间的通用解法。
761

被折叠的 条评论
为什么被折叠?



