当前位置: 首页 > news >正文

DeepSeek LeetCode 2842. 统计一个字符串的 k 子序列美丽值最大的数目 TypeScript实现

这是 LeetCode 2842 题"统计一个字符串的 k 子序列美丽值最大的数目"的 TypeScript 实现。

TypeScript 实现

```typescript
function countKSubsequencesWithMaxBeauty(s: string, k: number): number {
const MOD = 1000000007n;

// 1. 统计每个字母的频率
const freq: number[] = new Array(26).fill(0);
for (const char of s) {
freq[char.charCodeAt(0) - 97]++;
}

// 2. 过滤掉频率为0的字母,并降序排序
const frequencies: number[] = freq.filter(f => f > 0).sort((a, b) => b - a);

// 3. 如果字母种类不足k个,无法组成k长度的子序列
if (frequencies.length < k) {
return 0;
}

// 4. 找到第k大的频率(0-indexed)
const targetFreq: number = frequencies[k - 1];

// 5. 统计大于targetFreq和等于targetFreq的个数
let greaterCount: number = 0;
let equalCount: number = 0;
for (const f of frequencies) {
if (f > targetFreq) {
greaterCount++;
} else if (f === targetFreq) {
equalCount++;
}
}

// 6. 需要从equalCount中选取的数量
const needFromEqual: number = k - greaterCount;

// 7. 检查合法性
if (needFromEqual < 0 || needFromEqual > equalCount) {
return 0;
}

// 8. 计算组合数 C(equalCount, needFromEqual)
const combinations: bigint = nCk(equalCount, needFromEqual, MOD);

// 9. 计算贡献:targetFreq 的 needFromEqual 次方
let contribution: bigint = 1n;
for (let i = 0; i < needFromEqual; i++) {
contribution = (contribution * BigInt(targetFreq)) % MOD;
}

// 10. 计算最终结果
let result: bigint = (combinations * contribution) % MOD;

// 11. 乘以所有大于targetFreq的频率
for (const f of frequencies) {
if (f > targetFreq) {
result = (result * BigInt(f)) % MOD;
}
}

return Number(result);
}

/**
* 计算组合数 C(n, m) mod MOD
* @param n - 总数
* @param m - 选取数
* @param MOD - 模数
* @returns 组合数模 MOD 的结果
*/
function nCk(n: number, m: number, MOD: bigint): bigint {
if (m < 0 || m > n) return 0n;
if (m === 0 || m === n) return 1n;

// 使用更小的 m 进行计算,优化性能
m = Math.min(m, n - m);

let numerator: bigint = 1n;
let denominator: bigint = 1n;

for (let i = 0; i < m; i++) {
numerator = (numerator * BigInt(n - i)) % MOD;
denominator = (denominator * BigInt(i + 1)) % MOD;
}

// 使用费马小定理计算分母的逆元
return numerator * modPow(denominator, MOD - 2n, MOD) % MOD;
}

/**
* 快速幂算法(模运算)
* @param base - 底数
* @param exponent - 指数
* @param mod - 模数
* @returns base^exponent mod mod
*/
function modPow(base: bigint, exponent: bigint, mod: bigint): bigint {
let result: bigint = 1n;
let b: bigint = base % mod;
let exp: bigint = exponent;

while (exp > 0) {
if (exp & 1n) {
result = (result * b) % mod;
}
b = (b * b) % mod;
exp >>= 1n;
}

return result;
}
```

更优化的版本(使用预计算阶乘)

```typescript
function countKSubsequencesWithMaxBeauty(s: string, k: number): number {
const MOD = 1000000007n;

// 统计频率
const freq: number[] = new Array(26).fill(0);
for (const char of s) {
freq[char.charCodeAt(0) - 97]++;
}

// 过滤并排序
const freqs: number[] = freq.filter(f => f > 0).sort((a, b) => b - a);

if (freqs.length < k) return 0;

const target: number = freqs[k - 1];

// 统计大于和等于 target 的数量
let greater: number = 0;
let equal: number = 0;
for (const f of freqs) {
if (f > target) greater++;
else if (f === target) equal++;
}

const need: number = k - greater;
if (need < 0 || need > equal) return 0;

// 使用预计算的阶乘和逆元来计算组合数
const maxN: number = Math.max(equal, 26);
const [fact, invFact] = precomputeFactorials(maxN, MOD);

// 计算组合数 C(equal, need)
const combinations: bigint = fact[equal] * invFact[need] % MOD * invFact[equal - need] % MOD;

// 计算贡献
let result: bigint = combinations;
result = result * modPow(BigInt(target), BigInt(need), MOD) % MOD;

// 乘以所有大于 target 的频率
for (const f of freqs) {
if (f > target) {
result = result * BigInt(f) % MOD;
}
}

return Number(result);
}

/**
* 预计算阶乘和逆元
*/
function precomputeFactorials(maxN: number, MOD: bigint): [bigint[], bigint[]] {
const fact: bigint[] = new Array(maxN + 1);
const invFact: bigint[] = new Array(maxN + 1);

fact[0] = 1n;
for (let i = 1; i <= maxN; i++) {
fact[i] = fact[i - 1] * BigInt(i) % MOD;
}

// 使用费马小定理计算最大阶乘的逆元
invFact[maxN] = modPow(fact[maxN], MOD - 2n, MOD);

// 递推计算其他逆元
for (let i = maxN - 1; i >= 0; i--) {
invFact[i] = invFact[i + 1] * BigInt(i + 1) % MOD;
}

return [fact, invFact];
}

function modPow(base: bigint, exponent: bigint, mod: bigint): bigint {
let result: bigint = 1n;
let b: bigint = base % mod;
let exp: bigint = exponent;

while (exp > 0) {
if (exp & 1n) {
result = (result * b) % mod;
}
b = (b * b) % mod;
exp >>= 1n;
}

return result;
}
```

带有详细注释的版本

```typescript
/**
* LeetCode 2842. 统计一个字符串的 k 子序列美丽值最大的数目
* @param s - 输入字符串
* @param k - 子序列长度
* @returns 美丽值最大的 k 子序列的数目(模 10^9+7)
*/
function countKSubsequencesWithMaxBeauty(s: string, k: number): number {
const MOD = 1000000007n;

// Step 1: 统计每个小写字母的出现频率
const frequency: number[] = new Array(26).fill(0);
for (let i = 0; i < s.length; i++) {
const index: number = s.charCodeAt(i) - 97;
frequency[index]++;
}

// Step 2: 收集所有非零频率并按降序排序
const nonZeroFreqs: number[] = frequency.filter(f => f > 0);
nonZeroFreqs.sort((a, b) => b - a);

// Step 3: 边界检查 - 字母种类不足 k 个
if (nonZeroFreqs.length < k) {
return 0;
}

// Step 4: 找到第 k 大的频率值
const targetFreqValue: number = nonZeroFreqs[k - 1];

// Step 5: 统计大于和等于目标频率的字母数量
let greaterThanTarget: number = 0;
let equalToTarget: number = 0;

for (const f of nonZeroFreqs) {
if (f > targetFreqValue) {
greaterThanTarget++;
} else if (f === targetFreqValue) {
equalToTarget++;
}
}

// Step 6: 需要从频率等于目标值的字母中选择的数量
const needFromEqual: number = k - greaterThanTarget;

// Step 7: 验证选择数量是否合法
if (needFromEqual < 0 || needFromEqual > equalToTarget) {
return 0;
}

// Step 8: 计算组合数 C(equalToTarget, needFromEqual)
const combinations: bigint = calculateCombination(equalToTarget, needFromEqual, MOD);

// Step 9: 计算贡献值 (targetFreqValue ^ needFromEqual)
let contribution: bigint = fastPower(BigInt(targetFreqValue), BigInt(needFromEqual), MOD);

// Step 10: 计算结果 = 组合数 × 贡献值
let result: bigint = (combinations * contribution) % MOD;

// Step 11: 乘以所有大于目标频率的字母的频率值
for (const f of nonZeroFreqs) {
if (f > targetFreqValue) {
result = (result * BigInt(f)) % MOD;
}
}

return Number(result);
}

/**
* 计算组合数 C(n, m) mod MOD
* 使用乘法逆元方法
*/
function calculateCombination(n: number, m: number, MOD: bigint): bigint {
if (m < 0 || m > n) return 0n;
if (m === 0 || m === n) return 1n;

// 优化:使用较小的 m
m = Math.min(m, n - m);

let numerator: bigint = 1n;
let denominator: bigint = 1n;

// 计算分子和分母
for (let i = 0; i < m; i++) {
numerator = (numerator * BigInt(n - i)) % MOD;
denominator = (denominator * BigInt(i + 1)) % MOD;
}

// 返回 numerator * denominator^(-1) mod MOD
return numerator * fastPower(denominator, MOD - 2n, MOD) % MOD;
}

/**
* 快速幂算法(模运算)
* @param base - 底数
* @param exponent - 指数
* @param mod - 模数
* @returns base^exponent mod mod
*/
function fastPower(base: bigint, exponent: bigint, mod: bigint): bigint {
let result: bigint = 1n;
let b: bigint = base % mod;
let e: bigint = exponent;

while (e > 0) {
// 如果当前位是1,乘上当前底数
if (e & 1n) {
result = (result * b) % mod;
}
// 底数平方
b = (b * b) % mod;
// 指数右移一位
e >>= 1n;
}

return result;
}
```

测试用例

```typescript
// 测试示例
console.log(countKSubsequencesWithMaxBeauty("bcca", 2)); // 预期: 4
console.log(countKSubsequencesWithMaxBeauty("abbcd", 4)); // 预期: 2
console.log(countKSubsequencesWithMaxBeauty("aabbccdd", 4)); // 预期: 16

// 更多测试
console.log(countKSubsequencesWithMaxBeauty("abcde", 3)); // 预期: 60
console.log(countKSubsequencesWithMaxBeauty("aaaa", 2)); // 预期: 0 (字母不足)
```

算法复杂度

· 时间复杂度: O(n + 26 log 26) ≈ O(n)
· 空间复杂度: O(1)

类型安全特性

TypeScript 实现充分利用了类型系统:

1. 明确的类型注解:所有函数参数和返回值都有明确类型
2. BigInt 类型:正确处理大整数运算
3. 只读类型:可添加 readonly 修饰符确保数据不被意外修改
4. 元组返回类型:[bigint[], bigint[]] 明确表示返回阶乘和逆元数组

这个 TypeScript 实现类型安全、性能优良,能够正确解决原题。

http://www.cnnetsun.cn/news/2654542.html

相关文章:

  • 从GPT-Neo到FFmpeg:构建AI虚拟主播的完整技术栈解析
  • 现代网络安全实战框架:技术、流程与人员三大支柱解析
  • 路由器是工作在OSI模型**网络层(第3层)**的网络设备,其核心功能是根据数据包中的**目的IP地址**
  • SMUDebugTool:免费开源AMD Ryzen处理器调试工具完整指南
  • 综合算法 XXIX | 网络与算法
  • 如何高效管理Windows右键菜单:个性化定制完整教程
  • 别急着送修!Win10开机提示No Bootable Device?先试试这5个自救方法(含Boot Mode设置)
  • iOS 15+免越狱深度定制完全指南:CowabungaLite让你的iPhone与众不同
  • 提升效率300%的OneNote插件终极指南:160+功能完全解锁笔记生产力
  • Arduino双人连击游戏:从面包板原型到焊接成品的完整实践指南
  • 技术向善:从理念到实践,构建负责任的技术产品框架
  • ToDesk Linux客户端安装后,临时密码总变?手把手教你解读config.ini配置文件
  • 简历里还在用“精通”和“熟悉”?90%的人都错了,一招教你直接提升面试邀约率!
  • 终极指南:如何使用IwaraDownloadTool免费快速下载Iwara视频
  • VS2022里那个找类找成员的神器,原来藏在这里!手把手教你打开Class View
  • 从PromQL到Categraf指标:Grafana面板与告警规则迁移实战指南
  • 告别CAN总线8字节限制:手把手教你用AUTOSAR CANTP实现UDS长报文传输
  • 你的PCA图为什么发不了高分SCI?从TCGA数据谈谈RNA-seq降维的常见误区与优化技巧
  • 终极指南:如何轻松下载Iwara视频并管理你的收藏库
  • 别再问小程序怎么搞流式输出了!我用ThinkPHP5.0后端+uni-app,一个接口兼容H5和小程序
  • MT6709/MT6825编码器SPI通信深度解析:从数据手册到可复用的C语言驱动
  • 别再为STM8烧录发愁了!手把手教你用STVP+ST-Link搞定.hex文件(附常见问题排查)
  • 告别仿真!手把手教你用生成代码在真实硬件上跑通双向交错CCM图腾柱PFC(附实测波形与避坑指南)
  • Hitboxer终极指南:5分钟解决游戏输入冲突,提升操作精准度的专业工具
  • STM32F030驱动电机时,你的MOS管选对了吗?详解硬件保护电路设计与软件防烧录要点
  • 从星际DAO到地球治理:异步优先与本地自治的分布式组织设计
  • 相机都调麻了,缺陷还是漏检,问题到底卡在哪?
  • 保姆级教程:用Docker Compose一键部署PostgreSQL 16,再也不用记复杂命令了
  • 金融科技转型:AI与区块链如何重塑信贷风控与金融基础设施
  • 告别卡顿!用华为云ECS搭建高性能eNSP Pro实验平台(保姆级避坑指南)