2、尾部的零

问题描述: 设计一个算法,计算出n阶乘中尾部零的个数。
样例: 11!=39916800 11 ! = 39916800 ,因此应该返回 2。
解答(js实现):

const trailingZeros = function (n) {
    var sum = 0;
    var power = Math.floor(Math.log(n) / Math.log(5));
    for (var i=1;i<=power;i++) {
        sum += Math.floor(n / Math.pow(5, i));
    }
    return sum;
}

分析:思考一个数尾部的零是怎么得来的,乘以10对吧,所以n的阶乘尾部有多少零就可以看成它有多少个10这个因子,而10又可分解为两个质因数相乘 2×5 2 × 5 。将 n! n ! 分解为质因数相乘的形式,大概是这样:

n!=1×2×3×(2×2)×5×(2×3)×7×(2×2×2)×(3×3)×(2×5)... n ! = 1 × 2 × 3 × ( 2 × 2 ) × 5 × ( 2 × 3 ) × 7 × ( 2 × 2 × 2 ) × ( 3 × 3 ) × ( 2 × 5 ) . . .
显然,2的个数会比5的个数多,所以有多少个5就会有多少个10。故而问题就转化为寻找 n! n ! 的结果有多少个5这个因子。
  那么这个怎么找呢?我们看一下上边的等式,现在已经乘到10了,出现了2个5,想象一下继续乘下去的情况,到15时会再出现一个,到20时又会再出现一个,到25时则会出现2个,继续看下去,就会发现一个规律:
  (1) 每逢遇到5的倍数,就会出现一个;
  (2) 每逢遇到5的幂次方的倍数,就会出现5的幂次方的指数个,为了便于计算,我们把遇到5的幂次方的倍数时也算到5的倍数里,这时遇到5的幂次方的倍数就会出现 (5(51)) ( 5 的 幂 次 方 的 指 数 − ( 5 的 幂 次 方 的 指 数 − 1 ) ) 个,也就是1个。
  这样用文字来说会有点绕,我们不妨用代数式说明一下:

  • 5 5 的倍数,遇到一次就出现1个5,会遇到 n5 次,记作 n5×1=n5 n 5 × 1 = n 5 个;
  • 52 5 2 的倍数,遇到一次就会出现2个5,会遇到 n52 n 5 2 次,但是 52 5 2 的倍数又包含在 5 5 的倍数中,所以如果我们按2个算就会每次遇到都多计算1次,因此把那1次减去,就记作 n52×(21) 个;
  • 53 5 3 的倍数,遇到一次就会出现3个5,会遇到 n53 n 5 3 次,但是 53 5 3 的倍数又包含在 5 5 的倍数和 52 的倍数中,所以如果我们按3个算就会每次遇到都多计算2次,因此把那2次减去,就记作 n53×(32) n 5 3 × ( 3 − 2 ) 个;
  • 54 5 4 的倍数,同理,可记作 n54×(43) n 5 4 × ( 4 − 3 ) 个;
  • 依此类推下去。

  根据以上,我们不难得出一个公式:

Sum=n5+n52+n53+n54+ S u m = n 5 + n 5 2 + n 5 3 + n 5 4 + · · ·
  有了这个公式,问题就容易了,只需求出 log5n log 5 ⁡ n ,取整,然后将其作为循环次数累加就行了。
  js中有Math.log()函数,返回一个数的自然对数(loge, 即ln)。虽然没有直接求 log5n log 5 ⁡ n 的方法,但 log5n=lnnln5 log 5 ⁡ n = ln ⁡ n ln ⁡ 5 ,所以n(包括n)以内5的幂次方的最大指数可求得:

var power = Math.floor(Math.log(n) / Math.log(5));

  至此,我们从1开始循环到power,每次循环都加上公式中对应循环次数的项,即可得到结果:

var sum = 0;
for (var i=1;i<=power;i++) {
    sum += Math.floor(n / Math.pow(5, i));
}

总结:由以上的计算方式可以看出,power就是 lnn ln ⁡ n ,所以符合O(logN)的时间复杂度。在做这道题时,也找了不少资料,很多都提到求Sum的那个公式,但是具体怎么来的都没有很清晰地写思路,于是自己就试了试,期间也跑偏了几次,还好最后自圆其说地归纳出来了,希望对看到的朋友有所帮助。
  另外,还有一种思路也能按O(logN)的时间复杂度实现,这篇博客对于另一种思路写得很清晰,博主很用心,推荐一下 ^-^ 。

### C++ 中保留数字字符串尾部的方法 在处理数字并希望将其转换为字符串时保持尾随,通常涉及格式化输出或特定的数据类型操作。对于浮点数来说,可以通过设置流的精度来控制显示的小数位数[^1]。 当涉及到整数或其他类型的数值数据,并且这些数据作为字符串存储时,则需要采取不同的策略。一种常见的方式是在构建最终字符串之前,在原始数值上应用填充函数。例如: ```cpp #include <iomanip> #include <sstream> #include <iostream> int main() { double num = 20.5; // 使用 stringstream 和 setprecision 来保存指定数量的有效数字 std::stringstream streamObj; streamObj << std::fixed << std::setprecision(2); // 设置固定小数点后的两位有效数字 streamObj << num; std::string resultStr = streamObj.str(); std::cout << "Formatted Number with trailing zeros: " << resultStr << "\n"; } ``` 如果目标是确保某个长度内的整数始终具有固定的宽度(比如银行账户号码),那么可以利用 `setw()` 函数配合 `setfill('0')` 方法实现前置补的效果;而对于后置补的需求则相对少见,因为这往往意味着改变实际数值的意义。不过,如果是纯粹为了展示目的而不需要考虑数值意义的话,可以在转换成字符串之后再做相应的拼接工作。 另外需要注意的是,当使用标准库中的 `std::to_string` 将浮点型变量转为字符串时,默认情况下会去除不必要的尾随。因此在这种场景下应该采用上述提到过的更精细的方式来达到预期效果[^2]。 #### 处理纯数字字符串的情况 假设有一个仅由数字组成的字符串,并且想要在这个基础上增加额外的到末端而不影响其原有的值表达方式,可以直接通过简单的字符串连接完成此任务: ```cpp #include <string> #include <iostream> void appendTrailingZeros(std::string& number, size_t count) { while (count--) { number += '0'; } } // 测试代码片段 int main(){ std::string myNumber = "12345"; // 原始数字串 appendTrailingZeros(myNumber, 3); std::cout << "After appending three zeros at the end of string: " << myNumber << "\n"; } ``` 这种方法适用于那些只需要视觉上的变化而不是真正意义上的数值运算的情形。对于其他情况下的需求,可能还需要根据具体的应用背景做出调整。
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值