思路1:将所有数字转换成字符串,再遍历每个字符串的每一位。当n位数较大时,时间复杂度会比较高
思路2:与思路1相似,每次对10取模,然后判断个位数是否为1,当n位数大时,时间复杂度也比较高.
思路3及4:既然蛮力不好用,自然需要找规律,也就是1出现的规律。


首先附上一段思路1和2的代码,然后对思路3进行分析


public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        int count = 0;
        for(int i = 0; i <= n; i++) {
            String str = String.valueOf(i);
            for(int j = 0; j < str.length(); j++) {
                if(str.charAt(j) == '1')
                    count++;
            }
        }
        return count;
    }
}
public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        //count表示1出现的总次数
        int count = 0;
        //从1到n循环n次,对每一个数求它包含多少个1
        for (int i = 1; i <= n; i++) {
            int temp = i;
            //求这个数包含多少个1
            while (temp != 0) {   
                if (temp % 10 == 1) {
                    count++;
                }
                temp = temp / 10;
            }
        }
        return count;
    }
}

思路3:
很明显,在拿到比如34567这个数时,进行分段处理。
最直观的方式是分成1-9999和10000-19999和20000到34567三个部分来进行计算时,显然我们还需要将20000到34567继续分,分成(20000,29999)和(30000,34567),而(30000,34567)还需要分为(30000,33999)和(34000,34567),还要继续分,这种分法虽然有一定的规律性,但是写起来稍微复杂,因此我们考虑换一种分的方式
那就是将首位剥夺。即分成(1,4567)和(4568,34567),而4567再分成(1,567)和(568,4567),而(1,567)再分成(1,67)和(68,567)....
显然第二种分法规律性更强,分成的组数也更少,可以使用递归.(看起来比较费劲,先mark一下,以后再来学习)

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        if(n<=0||n==1){
            return n;
        }
    String str=n+"";              
        return numberOf1(str);  
    }

    public static int numberOf1(String str){
        char[] strN=str.toCharArray();
        int length=strN.length;       
        if(length==1&&strN[0]=='0'){
            return 0;
        }
        if(length==1&&strN[0]>'1'){
            return 1;
        }
        int numFirstDigit=0;
        if(strN[0]>'1')
            numFirstDigit=(int) Math.pow(10,length-1);
        else if(strN[0]=='1')
            numFirstDigit=Integer.parseInt(str.substring(1))+1;
        int numOtherDigits=(int) ((strN[0]-'0')*(length-1)*Math.pow(10, length-2));        
        int numRecursive=numberOf1(str.substring(1));   
        return numFirstDigit+numOtherDigits+numRecursive;

    } 

}

4.再考虑一种新思路。统计某个位置上 1出现的次数。如34,1在十位上出现的次数是10次
(10到19),1在个位上出现的次数是4次(1,11,21,31),因此34中1出现了14次。

对于整数n,将这个整数分为三部分:当前位数字cur,更高位数字high,更低位数字low,如:对于n=21034,当位数是十位时,cur=3,high=210,low=4。
我们从个位到最高位 依次计算每个位置出现1的次数:
在计算时,会出现三种情况
1)当前位的数字等于0时,例如n=21034,在百位上的数字cur=0,百位上是1的情况有:00100-00199,01100-01199,……,20100-20199。一共有21*100种情况,即high*100;
2)当前位的数字等于1时,例如n=21034,在千位上的数字cur=1,千位上是1的情况有:01000-01999,11000-11999,21000-21034。一共有2*1000+(34+1)种情况,即high*1000+(low+1)。
3)当前位的数字大于1时,例如n=21034,在十位上的数字cur=3,十位上是1的情况有:00010-00019,……,21010-21019。一共有(210+1)*10种情况,即(high+1)*10。

public class Solution {
    public int NumberOf1Between1AndN_Solution(int n) {
        int count=0;
        for(int i=1;i<=n;i*=10){  //i代表位数
            int high=n/(i*10); //更高位数字
            int low=(n%i);  //更低位数字
            int cur=(n/i)%10;  //当前位数字
            if(cur==0){
                count+=high*i;
            }else if(cur==1){
                count+=high*i+(low+1);
            }else{
                count+=(high+1)*i;
            }
        }
        return count;
    }
}

综合来看,最后一种思路还是容易想到,也有大佬针对最后一种思路进行了优化,这里就不赘述了,毕竟初学者先消化思路4的初始写法就很OK了。