Hard
题目描述
给定两个正整数 left 和 right,满足 left <= right。计算区间 [left, right] 内所有整数的乘积。
由于乘积可能非常大,你需要按照以下步骤对其进行缩写:
计算乘积中所有尾随零的数量并移除它们。将此数量记为
C。- 例如,1000 有 3 个尾随零,546 有 0 个尾随零。
设移除所有尾随零后剩余的位数为
d。如果d > 10,则将乘积表示为<pre>...<suf>,其中<pre>表示乘积的前 5 位数字,<suf>表示移除所有尾随零后的后 5 位数字。如果d <= 10,保持不变。- 例如,我们将 1234567654321 表示为 12345…54321,但 1234567 仍表示为 1234567。
最终,将乘积表示为字符串
"<pre>...<suf>eC"。- 例如,12345678987600000 将表示为 “12345…89876e5”。
返回一个字符串,表示区间 [left, right] 内所有整数乘积的缩写形式。
示例 1:
输入:left = 1, right = 4
输出:"24e0"
解释:乘积是 1 × 2 × 3 × 4 = 24。
没有尾随零,所以 24 保持不变。缩写将以 "e0" 结尾。
由于位数是 2,小于 10,我们不需要进一步缩写。
因此,最终表示是 "24e0"。
示例 2:
输入:left = 2, right = 11
输出:"399168e2"
解释:乘积是 39916800。
有 2 个尾随零,移除后得到 399168。缩写将以 "e2" 结尾。
移除尾随零后的位数是 6,所以我们不进一步缩写。
因此,缩写乘积是 "399168e2"。
示例 3:
输入:left = 371, right = 375
输出:"7219856259e3"
解释:乘积是 7219856259000。
约束:
1 <= left <= right <= 10^4
解题思路
这是一道需要处理大数运算和数学技巧的困难题目。我们需要分别计算三个部分:尾随零的数量、后五位数字和前五位数字。
核心思路:
计算尾随零数量 C:
- 尾随零由因子 2 和 5 的配对产生,数量等于 min(因子2的个数, 因子5的个数)
- 通常因子 2 比因子 5 多,所以只需计算因子 5 的个数
计算后五位数字:
- 在计算乘积时,同时移除配对的因子 2 和 5(避免产生尾随零)
- 对剩余部分取模 10^5 得到后五位
计算前五位数字:
- 使用对数性质:log₁₀(a×b) = log₁₀(a) + log₁₀(b)
- 计算 sum = Σlog₁₀(i) for i in [left, right]
- 设 sum = integer_part + fractional_part
- 前五位 = floor(10^(fractional_part + 4))
格式化输出:
- 如果移除尾随零后位数 ≤ 10,直接输出完整数字
- 否则输出 “前五位…后五位eC” 格式
这种方法避免了直接计算可能溢出的大数乘积,而是巧妙地分别处理各个组成部分。
代码实现
class Solution {
public:
string abbreviateProduct(int left, int right) {
// 计算因子5的数量(尾随零数量)
int trailingZeros = 0;
for (int i = left; i <= right; i++) {
int num = i;
while (num % 5 == 0) {
trailingZeros++;
num /= 5;
}
}
// 计算后五位数字(移除配对的2和5)
long long suffix = 1;
int removed2 = 0, removed5 = 0;
const long long MOD = 100000;
for (int i = left; i <= right; i++) {
long long num = i;
// 移除因子5
while (num % 5 == 0 && removed5 < trailingZeros) {
num /= 5;
removed5++;
}
// 移除因子2
while (num % 2 == 0 && removed2 < trailingZeros) {
num /= 2;
removed2++;
}
suffix = (suffix * num) % MOD;
}
// 计算总位数
double logSum = 0;
for (int i = left; i <= right; i++) {
logSum += log10(i);
}
int totalDigits = (int)logSum + 1 - trailingZeros;
if (totalDigits <= 10) {
return to_string(suffix) + "e" + to_string(trailingZeros);
}
// 计算前五位数字
double fractionalPart = logSum - floor(logSum);
long long prefix = (long long)pow(10, fractionalPart + 4);
return to_string(prefix) + "..." + string(5 - to_string(suffix).length(), '0') + to_string(suffix) + "e" + to_string(trailingZeros);
}
};
class Solution:
def abbreviateProduct(self, left: int, right: int) -> str:
import math
# 计算因子5的数量(尾随零数量)
trailing_zeros = 0
for i in range(left, right + 1):
num = i
while num % 5 == 0:
trailing_zeros += 1
num //= 5
# 计算后五位数字(移除配对的2和5)
suffix = 1
removed_2 = removed_5 = 0
MOD = 100000
for i in range(left, right + 1):
num = i
# 移除因子5
while num % 5 == 0 and removed_5 < trailing_zeros:
num //= 5
removed_5 += 1
# 移除因子2
while num % 2 == 0 and removed_2 < trailing_zeros:
num //= 2
removed_2 += 1
suffix = (suffix * num) % MOD
# 计算总位数
log_sum = sum(math.log10(i) for i in range(left, right + 1))
total_digits = int(log_sum) + 1 - trailing_zeros
if total_digits <= 10:
return str(suffix) + "e" + str(trailing_zeros)
# 计算前五位数字
fractional_part = log_sum - math.floor(log_sum)
prefix = int(10 ** (fractional_part + 4))
suffix_str = f"{suffix:05d}"
return f"{prefix}...{suffix_str}e{trailing_zeros}"
public class Solution {
public string AbbreviateProduct(int left, int right) {
// 计算因子5的数量(尾随零数量)
int trailingZeros = 0;
for (int i = left; i <= right; i++) {
int num = i;
while (num % 5 == 0) {
trailingZeros++;
num /= 5;
}
}
// 计算后五位数字(移除配对的2和5)
long suffix = 1;
int removed2 = 0, removed5 = 0;
const long MOD = 100000;
for (int i = left; i <= right; i++) {
long num = i;
// 移除因子5
while (num % 5 == 0 && removed5 < trailingZeros) {
num /= 5;
removed5++;
}
// 移除因子2
while (num % 2 == 0 && removed2 < trailingZeros) {
num /= 2;
removed2++;
}
suffix = (suffix * num) % MOD;
}
// 计算总位数
double logSum = 0;
for (int i = left; i <= right; i++) {
logSum += Math.Log10(i);
}
int totalDigits = (int)logSum + 1 - trailingZeros;
if (totalDigits <= 10) {
return suffix + "e" + trailingZeros;
}
// 计算前五位数字
double fractionalPart = logSum - Math.Floor(logSum);
long prefix = (long)Math.Pow(10, fractionalPart + 4);
string suffixStr = suffix.ToString("D5");
return $"{prefix}...{suffixStr}e{trailingZeros}";
}
}
var abbreviateProduct = function(left, right) {
let twos = 0, fives = 0;
let logSum = 0;
// Count factors of 2 and 5, and calculate log sum
for (let i = left; i <= right; i++) {
let num = i;
while (num % 2 === 0) {
twos++;
num /= 2;
}
num = i;
while (num % 5 === 0) {
fives++;
num /= 5;
}
logSum += Math.log10(i);
}
let trailingZeros = Math.min(twos, fives);
twos -= trailingZeros;
fives -= trailingZeros;
// Calculate remaining digits
let remainingDigits = Math.floor(logSum) + 1;
if (remainingDigits <= 10) {
// Calculate exact product without trailing zeros
let product = 1n;
for (let i = left; i <= right; i++) {
product *= BigInt(i);
}
// Remove trailing zeros
while (product % 10n === 0n) {
product /= 10n;
}
return product.toString() + "e" + trailingZeros;
} else {
// Need to abbreviate
// Calculate first 5 digits using logarithms
let fractionalPart = logSum - Math.floor(logSum);
let first5 = Math.floor(Math.pow(10, fractionalPart + 4));
// Calculate last 5 digits using modular arithmetic
let last5 = 1;
const MOD = 100000;
for (let i = left; i <= right; i++) {
last5 = (last5 * i) % MOD;
}
// Remove factors of 2 and 5 from last5
for (let i = 0; i < twos; i++) {
last5 = (last5 * 2) % MOD;
}
for (let i = 0; i < fives; i++) {
last5 = (last5 * 5) % MOD;
}
// Format last5 to 5 digits
let last5Str = last5.toString().padStart(5, '0');
return first5 + "..." + last5Str + "e" + trailingZeros;
}
};
复杂度分析
| 复杂度类型 | 时间复杂度 | 空间复杂度 |
|---|---|---|
| 总体 | O(n log n) | O(1) |
详细分析:
- 时间复杂度:O(n log n),其中 n = right - left + 1
- 计算因子5的数量:O(n log n),每个数字最多除以5约log₅(n)次
- 计算后五位:O(n log n),每个数字最多除以2和5各约log(n)次
- 计算对数和:O(n)
- 其他操作:O(1)
- 空间复杂度:O(1),只使用常数级别的额外空间
相关题目
. Factorial Trailing Zeroes (Medium)
. Find All Good Indices (Medium)