Java详解剑指offer面试题43--1~n整数中1出现的次数

Java详解剑指offer面试题43–1~n整数中1出现的次数

输入一个整数n,求1~n这n个整数的十进制表示中1出现的次数,例如输入12, 1~12中出现1的有1、10、11、12共5次

计算每个数字出现1的次数

比较直接的思路就是写一个方法可以统计任意整数1的个数,然后用一个循环得到对1~n每一个数调用该方法统计总的1的出现次数。

package Chap5;

public class NumOf1 {
    /**
     * 方法1,计算每个数字中1的个数,复杂度O(nlgn)
     */
    public int NumberOf1From1To(int n) {
        // 正负数不影响1的个数,统一变成非负数
        if (n < 0) n = Math.abs(n);

        int count = 0;
        // 循环求n个数字,共O(nlgn)的时间
        for (int i = 1; i <= n; i++) {
            count += numOf1(i);
        }
        return count;
    }

    /**
     * O(lgn)的复杂度求一个数中含有1的数量
     */
    private int numOf1(int n) {
        int count = 0;
        while (n != 0) {
            if (n % 10 == 1) count++;
            n = n / 10;
        }
        return count;
    }
}

numOf1方法中,n % 10可以得到个位,n = n / 10表示丢弃最低位。该方法的复杂度为O(lgn),对于1~n中n个数字都要调用一遍该方法,总的时间复杂度是O(nlgn)。

更暴力的解法–StringBuilder拼接

使用StringBuilder将1~n的所有数无缝拼接起来,然后一个个数。

package Chap5;

public class NumOf1 {
    /**
     * 方法2:使用StringBuilder将所有数字拼接,无脑数数
     */
    public int numOf1Between1AndN(int n) {
        // 正负数不影响1的个数,统一变成非负数
        if (n < 0) n = Math.abs(n);

        StringBuilder sb = new StringBuilder();
        for (int i = 0; i < n; i++) {
            sb.append(i);
        }

        int count = 0;
        for (int i = 0; i < sb.length(); i++) {
            if (sb.charAt(i) == '1') {
                count++;
            }
        }
        return count;
    }
}

神一样的方法

参考LeetCode

1可以出现在任意一位,比如3245,1出现在个、十、百、千位都可以。只要固定某一位为1,计算出该位是1的所有情况,将固定每一位得到的情况数相加就是最终结果。

要固定某一位为1,可以使用m = 1, 10, 100, 1000…,对n作除、余操作,将输入整数分为高位和低位两部分。举个例子,对于输入n = 3101592,m = 100,如果令a = n / m, b = n % m,将得到a = 31015,b= 92两部分,现在固定百位为1(始终固定a的最低位),即xxxx1xx这样的形式,这样形式的数有多少个呢?

0000 1 00
0000 1 01
.....
0000 1 99
0001 1 00
......
0001 1 99
0002 1 00
......
0002 1 99
......
3101 1 00
......
3101 1 99

为了看得直观,上面刻意将数字从百位处分隔开,对于百位之前的高位数,总共有0000~3101共3102种情况,而每一种情况对应着低位有00~99共100种情况,因此百位为1的情况数是3102*100,也就是(a / 10 + 1) * m种情况。好,现在得到百位为1的情况数了,个位与千位等其他位计算方法和上面类似,只需取不同的m就能将输入的整数分成两部分并固定某一位为1.

接下来m = 1000时,3101592被分成a = 3101和b = 592两部分,现在固定千位为1,但是此时千位本来就是1了,来看和上面有什么不同

000 1 000
......
000 1 999
309 1 000
......
309 1 999
310 1 000
......
310 1 592

可以看到千位前的高位从000~309和上面一样,每一种情况都有000~999种可能,但是到310时,后面最多只能到592,共000~592是593种情况。此时千位为1的情况总数为:310 * 1000 + 593,即当前要被固定的位在输入中本来就是1的话有(a / 10) * m + b + 1种情况。

再看m = 10000,固定万位的情况。a = 310, b = 1592.

00 1 0000
......
00 1 9999
30 1 0000
......
30 1 9999

没有了,共31 * 10000种,即当前要被固定的位在输入中是0的话有(a / 10) * m种情况。

一开始固定百位其实就是当前要被固定的位在输入中是2~9这种情况。

分析得差不多了,现在考虑这三种情况计算就好了。

/**
 * 方法三
 */
public int numberOf1(int n) {
  	int ones = 0;
  	for (long m = 1; m <= n; m *= 10) {
    	long a = n / m;
    	long b = n % m;
    	if (a % 10 == 0) ones += a / 10 * m;
    	else if (a % 10 == 1) ones += (a / 10 * m) + (b + 1);
    	else ones += (a / 10 + 1) * m;
  	}
  	return ones;
}

本文参考文献:
[1]github.com/haiyusun/data-structures

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

快乐李同学(李俊德-大连理工大学)

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值