我来为你提供 LeetCode 3145「大数组元素的乘积」的 TypeScript 实现。
TypeScript 实现
```typescript
function findProductsOfElements(queries: number[][]): number[] {
const MOD = 1e9 + 7;
// 快速幂:计算 a^b % mod
function qpow(a: bigint, b: bigint, mod: number): number {
let res: bigint = 1n % BigInt(mod);
a = a % BigInt(mod);
while (b > 0n) {
if ((b & 1n) === 1n) {
res = res * a % BigInt(mod);
}
a = a * a % BigInt(mod);
b >>= 1n;
}
return Number(res);
}
/**
* 计算 big_nums 前 k 个元素的幂次之和
* big_nums 中每个元素都是 2^i 的形式,乘积 = 2^(所有幂次之和)
*/
function calc(k: bigint): bigint {
let res: bigint = 0n; // 幂次之和
let n: bigint = 0n; // 当前确定的数字
let cnt1: bigint = 0n; // 已填的1的个数
let sum_i: bigint = 0n; // 已填的1的位下标之和
// 从高位到低位试填
// 最高可能的位数:找到最大的 i 使得 (cnt1 << i) + (i << (i-1)) 有意义
// 对于 k <= 1e15,最多约 50 位
let maxBit = 0;
let temp = k;
while (temp > 0n) {
maxBit++;
temp >>= 1n;
}
maxBit = Math.max(maxBit, 1);
for (let i = maxBit; i > 0; i--) {
const bi = BigInt(i);
const twoPowI = 1n << bi; // 2^i
const twoPowI_1 = 1n << (bi - 1n); // 2^(i-1)
// 如果在第 i 位填1,新增的幂次个数
const c = (cnt1 << bi) + (bi * twoPowI_1);
if (c <= k) {
k -= c;
// 新增幂次之和
res += (sum_i << bi) + ((bi * (bi - 1n) / 2n) * twoPowI_1);
sum_i += bi;
cnt1 += 1n;
n |= 1n << bi;
}
}
// 最低位(i = 0)单独处理
if (cnt1 <= k) {
k -= cnt1;
res += sum_i; // 最低位填1,已有1贡献0次幂(2^0=1,指数为0)
n |= 1n;
}
// 剩余 k 个幂次,由 n 的低 k 个1的位下标补充
while (k > 0n) {
// n & -n 得到最低位的1
const lowbit = n & (-n);
// 该1的位下标
const tz = BigInt(ctz(Number(lowbit)));
res += tz;
// 去掉最低位的1
n &= n - 1n;
k -= 1n;
}
return res;
}
// 计算二进制末尾0的个数(ctz: count trailing zeros)
function ctz(x: number): number {
if (x === 0) return 0;
let count = 0;
while ((x & 1) === 0) {
count++;
x >>= 1;
}
return count;
}
const ans: number[] = [];
for (const [from, to, mod] of queries) {
const power = calc(BigInt(to) + 1n) - calc(BigInt(from));
ans.push(qpow(2n, power, mod));
}
return ans;
}
```
更简洁的版本(参考灵茶山艾府的试填法)
```typescript
function findProductsOfElements(queries: number[][]): number[] {
// 快速幂
function qpow(a: bigint, b: bigint, mod: number): number {
let res = 1n % BigInt(mod);
a %= BigInt(mod);
while (b > 0n) {
if ((b & 1n) === 1n) res = res * a % BigInt(mod);
a = a * a % BigInt(mod);
b >>= 1n;
}
return Number(res);
}
// 计算 big_nums 前 k 个元素的幂次之和
function calc(k: bigint): bigint {
let res = 0n;
let n = 0n; // 当前构造的数字
let cnt1 = 0n; // n 中1的个数
let sum_i = 0n; // n 中1的位下标之和
// 从高位到低位试填
for (let i = 50; i > 0; i--) {
const bi = BigInt(i);
const add = (cnt1 << bi) + (bi << (bi - 1n)); // 填1新增的幂次个数
if (add <= k) {
k -= add;
res += (sum_i << bi) + ((bi * (bi - 1n) / 2n) << (bi - 1n));
sum_i += bi;
cnt1 += 1n;
n |= 1n << bi;
}
}
// 最低位
if (cnt1 <= k) {
k -= cnt1;
res += sum_i;
n |= 1n;
}
// 剩余 k 个
while (k > 0n) {
const lowbit = n & -n;
res += BigInt(Math.log2(Number(lowbit)));
n ^= lowbit;
k -= 1n;
}
return res;
}
return queries.map(([from, to, mod]) => {
const power = calc(BigInt(to) + 1n) - calc(BigInt(from));
return qpow(2n, power, mod);
});
}
```
核心思路
```
big_nums: 每个正整数的强数组(二进制中1对应的2^i)拼接而成
1 -> [1] -> 幂次: [0]
2 -> [2] -> 幂次: [1]
3 -> [1,2] -> 幂次: [0,1]
4 -> [4] -> 幂次: [2]
5 -> [1,4] -> 幂次: [0,2]
...
big_nums 中每个元素都是 2^i,所以乘积 = 2^(幂次之和)
calc(k): 求前 k 个元素的幂次之和(试填法从高位到低位构造数字)
```
复杂度
指标 复杂度
时间 O(q \cdot \log U),约 O(q \cdot 50)
空间 O(1)
TypeScript 中使用 `BigInt` 处理大数,因为 k 最大可达 10^{15},需要 64 位整数精度。