【todo】【重点】【特例】剑指offer——面试题32:从1到n整数中1出现的次数

力扣

解法1:字符串统计字符

class Solution {
    public int countDigitOne(int n) {
        int res = 0;
        for (int i = 1; i <= n; ++i) {
            char[] array = String.valueOf(i).toCharArray();
            res += count(array);
        }

        return res;
    }

    public int count(char[] array) {
        int res = 0;
        for (char c : array) {
            if (c == '1') {
                ++res;
            }
        }

        return res;
    }
}

解法2:找规律

下面solution3的重写为java版代码。思路在下面,重点思路截图在这里:
在这里插入图片描述

class Solution {
    public int countDigitOne(int n) {
        int round = n, weight = 0, base = 1, res = 0;
        while (round > 0) {
            weight = round % 10;
            round = round / 10;
            if (weight == 0) {
                res += round * base;
            } else if (weight == 1) {
                res += round * base + (n % base) + 1;
            } else {
                res += round * base + base; 
            }

            base *= 10;
        }

        return res;
    }
}

##Solution1:
最容易想到的方法:

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n){
        int res = 0;
        for(int i = 1; i <= n; i++)
            res += NumOfOne(i);
        return res;
    }
    int NumOfOne(int n){
        int temp = 0, yushu = 0;
        while (n != 0) {
            yushu = n%10;
            if (yushu == 1)
                temp++;
            n /= 10;
        }
        return temp;
    }
};

##Solution2:
利用字符串查找的方法对solution1稍加修改
主要学习在string对象中利用count函数统计某个字符出现个数的用法。

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n) {
        int res = 0;
        for (int i = 1; i <= n; i++)
            res += NumOfOne(i);
        return res;
    }
    int NumOfOne(int n) {
        string str = to_string(n);
        int nums = count(str.begin(), str.end(), '1');
        return nums;
    }
};

##Solution3:
参考网址:http://blog.youkuaiyun.com/yi_afly/article/details/52012593。把大神的博客粘贴在这里。。
大神的思路就是厉害啊!
在看大神的解题思路前说一下自己的感想:
看完这个思路后很容易就产生某些数如11,111等会被重复统计的情况。首先要看清题目要求:是统计从1到n整数中1出现的次数,而非统计含有1的整数有多少个。该算法的思想是依次统计1在个、十、百、千等位置上的出现次数。11的确会被统计两次,111也会被统计三次。拿111为例:当111被第一次统计时,是在统计个位上出现的一次1;当111被第二次统计时,是在统计十位上出现的一次1;当111被第三次统计时,是在统计百位上出现的一次1,故而对于1出现的总次数而言,并不存在重复计数的情况。
试想如果题目是:统计从1到n整数中含有1的整数,应该怎么做呢?我的第一想法是把所有的数字变为string对象,然后查找字符‘1’,理论上复杂度是 O ( n ) O(n) O(n)。不知道有木有更好的算法,有的话不吝赐教~
解题思路
考虑将n的十进制的每一位单独拿出讨论,每一位的值记为weight。
1.个位
从1到n,每增加1,weight就会加1,当weight加到9时,再加1又会回到0重新开始。那么weight从0-9的这种周期会出现多少次呢?这取决于n的高位是多少,看图:
这里写图片描述
以534为例,在从1增长到n的过程中,534的个位从0-9变化了53次,记为round。每一轮变化中,1在个位出现一次,所以一共出现了53次。
再来看weight的值。weight为4,大于0,说明第54轮变化是从0-4,1又出现了1次。我们记1出现的次数为count,所以:
count = round+1 = 53 + 1 = 54

如果此时weight为0(n=530),说明第54轮到0就停止了,那么:
count = round = 53

2.十位
对于10位来说,其0-9周期的出现次数与个位的统计方式是相同的,见图:
这里写图片描述
不同点在于:从1到n,每增加10,十位的weight才会增加1,所以,一轮0-9周期内,1会出现10次。即round x 10。
再来看weight的值。当此时weight为3,大于1,说明第6轮出现了10次1,则:
count = round x 10 + 10 = 5 x 10 + 10 = 60

如果此时weight的值等于0(n=504),说明第6轮到0就停止了,所以:
count = round x 10 + 0 = 5 x 10 = 50

如果此时weight的值等于1(n=514),那么第6轮中1出现了多少次呢?很明显,这与个位数的值有关,个位数为k,第6轮中1就出现了k+1次(0至k,共k+1个数)。我们记个位数为former,则:
count = round x 10 + (former +1)= 5 x 10+(4+1) = 55

3.更高位
更高位的计算方式其实与十位是一致的,不再阐述。

4.总结
将n的各个位分为两类:个位与其它位。
对个位来说:

若个位大于0,1出现的次数为round x 1 + 1
若个位等于0,1出现的次数为round x 1
对其它位来说,记每一位的权值为base,位值为weight,该位之前的数是former,举例如图:
这里写图片描述
则:
若weight为0,则1出现次数为round x base
若weight为1,则1出现次数为round x base + former + 1
若weight大于1,则1出现次数为rount x base + base
比如:

534 = (个位1出现次数)+(十位1出现次数)+(百位1出现次数)=(53 x 1 + 1)+(5 x 10 + 10)+(0 x 100 + 100)= 214
530 = (53 x 1)+(5 x 10 + 10)+(0 x 100 + 100) = 213
504 = (50 x 1 + 1)+(5 x 10)+(0 x 100 + 100) = 201
514 = (51 x 1 + 1)+(5 x 10 + 4 + 1)+(0 x 100 + 100) = 207
10 = (1 x 1) + (0 x 10 + 0 + 1) = 2
完整代码

class Solution {
public:
    int NumberOf1Between1AndN_Solution(int n) {
        if (n < 1) return 0;
        int count = 0, base = 1, round = n;
        while (round > 0) {
            int weight = round % 10;
            round /= 10;
            count += round * base;
            if (weight == 1)
                count += (n % base) + 1;
            else if (weight > 1)
                count += base;
            base *= 10;
        }
        return count;
    }
};

时间复杂度分析
由分析思路或者代码都可以看出,while循环的次数就是n的位数, l o g n logn logn(以10为底),而循环体内执行的操作都是有限次的,所以时间复杂度为 O ( l o g n ) O(logn) O(logn)
真是一个好思路啊~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值