在csdn上面看到的题目

csdn上面看到一道题目:
对于任意正整数nfn)表示从1n中数字1出现的次数。例如f(3)=1;   f(11)=4;   f(13)=6;
设计一个算法来计算f(n);
最开始想到的算法是最直接的算法:对1n的每个数字m,将m转换成字符串,计算该字符串中字符‘1’出现的次数。所有的次数的和就是计算的结果。具体算法如下:
 
public long countAppearTimes(long n)
{
long appearTimes = 0;
for long i = 1; i <= n; i++
{
   String strN = String.valueOf(i);
   int length = strN.length();
   for (int i=0; i< length; i++)
   {
        if (strN.charAt(i) == ‘1’)
        {
           appearTimes = appearTimes + 1;
        }
   }
}
return appearTimes;
}
这个算法比较直接,也比较好理解,但是效率太低下。当n比较小的时候还显示不出来,但是当n比较大的时候,就十分明显了。在我的机器上,当n999999999时,运行了540047ms,也就是9分钟的时间。
      是否有比较简单、高效的算法呢?
我们来做一下转换。假设给出的n是一个m位的数字,可以表示为
a1a2a3a4a5a6......am。那么将从1n的所有的数字都按m位编写,不足的在左端补0。具体形式如下
0         0          0         0      ......     0           0          1
0         0          0         0      ......     0           0          2
......
0         0          0         0      ......     0           1          0
0         0          0         0      ......     0           1          1
......
a1     a2     a3         a4     。。。。。。         am2         am-1                   am
我们可以发现以下的规律:
个位上的数字,每10个中就有11
十位上的数字,每100个中有101;(10——19
百位上的数字,每1000个中有1001;(100——199
依次类推,在从前向后数的第i位上的数字,每10m-i+1个中就有10m-i1
因此,对于n= a1a2a3a4a5a6......am;第i位的数字为ai
1.     如果ai 等于0,那么第i位上有 a1a2a3a4a5a6...... ai-1 * 10 m-i 1
也就是说,如果ai 等于0,那么第i位上的1的个数为n/10m-i+1*10m-i1
注意,/表示整除,例如132/10 13。以下相同
例如:n13059;第三位为0。那么第三位上的1共有
13059/105-3+1*105-3=1300个。即
100——1991100——11992100——21993100——31994100——41995100——51996100——61997100——71998100——8199......13100——13199
2.     如果ai等于1,那么从1a1a2a3a4a5a6... ai-1099...9,i位上的1的个数可以根据情况1计算出来。从a1a2a3a4a5a6... ai-1100...0a1a2a3a4a5a6... ai-11 ai1 ai2... am 中第i位上的1的个数为 ai1 ai2... am+1,n%10m-i + 1个。(%表示取余,例如132 %10 = 2。以下相同)所以第i位上共有n/10m-i+1*10m-i + n%10m-i + 11
3.     如果ai位是其他的数字,那么第i位上共有n/10m-i+1*10m-i + 10m-i  =(n/10m-i+1+1)* 10m-i1。具体的推算各位可以自己去验证。
根据这思想,就有以下的算法。
public long countAppearTimes(long n){
        long times = 0;
        String str = String.valueOf(n);
        int length = str.length();
        for (int i=0; i<length; i++){
//注意这里i是从0开始的,上面的算法描述中的是从1开始的,因此以下的程序稍有不同。
            long exp = Math.round(Math.pow(10, length-i-1));
            long mod = n % exp;
            long div = n / exp;
            long tempTimes = 0;
          
            if (str.charAt(i)=='1'){
                tempTimes = mod + 1;
                tempTimes = tempTimes + (div -1) * exp / 10;
                times = times + tempTimes;
            }
            else{
                if (str.charAt(i) == '0')
                    tempTimes = tempTimes + div / 10 * exp;
                else
                    tempTimes = tempTimes + (div /10 +1)*exp;
                times = times + tempTimes;
            }
        }
        return times;
    }
通过验证,这个算法和最原始的算法的结果是完全相同的(验证了从199999999)。
这个算法的效率如何呢?当n999999999时,运行了109ms
![](https://csdnimg.cn/release/static/images/moreno-blue.png) 优快云 AI 助手 您好,我是由优快云和外部合作伙伴合作研发的AI作业模型机器人,专注于协助您解决各类学习与编程问题。以下是根据您提供的题目要求,使用 Python 实现的完整解答。 --- ### **题目重述** 输入一个正整数 $N$,输出区间 $[1, N]$ 中所有能被 3 或 5 整除的整数,每行输出 5 个,数字之间用水平制表符 `\t` 分隔,最后输出满足条件的数字总个数。 --- ### **给出答案(答案为带注释的代码块)** ```python # 读取输入的正整数 N N = int(input()) # 存储符合条件的数 numbers = [] # 遍历从 1 到 N 的所有整数 for i in range(1, N + 1): # 判断是否能被 3 或 5 整除 if i % 3 == 0 or i % 5 == 0: numbers.append(i) # 每行输出最多 5 个数,用 \t 分隔 for i in range(0, len(numbers), 5): print('\t'.join(map(str, numbers[i:i + 5]))) # 输出总数 print(len(numbers)) ``` --- ### **代码解析** 1. `N = int(input())`:获取用户输入并转换为整数。 2. 创建空列表 `numbers` 用于收集满足条件的数值。 3. 使用 `for` 循环遍历 $[1, N]$ 区间内的每一个整数。 4. 利用模运算符 `%` 判断当前数是否能被 3 或 5 整除,若成立则加入列表。 5. 使用切片 `range(0, len(numbers), 5)` 控制每行输出 5 个元素,并通过 `'\t'.join(...)` 将其以制表符连接成字符串输出。 6. 最后打印出符合条件的数字总个数。 --- ### **知识点(列出该代码中遇到的知识点)** - **模运算与条件判断**:使用 `%` 判断整除性,结合 `or` 实现逻辑“或”关系。 - **循环与列表操作**:`for` 循环遍历区间,`append` 动态添加元素至列表。 - **字符串与输出格式控制**:利用 `join` 和 `\t` 实现制表符分隔,切片控制每行输出数量。
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值