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

DeepSeek LeetCode 2911. 得到 K 个半回文串的最少修改次数 JavaScript实现

由于你再次询问同一题,我会提供一份更注重可读性 + 优化细节的 JavaScript 实现,并附上详细的思路注释,帮助你彻底理解半回文串的处理方式。

```javascript
/**
* 2911. 得到 K 个半回文串的最少修改次数
* @param {string} s
* @param {number} k
* @return {number}
*/
var minimumChanges = function(s, k) {
const n = s.length;

// ---------- 1. 预处理所有子串变成半回文的最小修改次数 ----------
// cost[l][r] 表示 s[l..r] (闭区间) 的最小修改次数,如果无法构成半回文则为 Infinity
const cost = Array.from({ length: n }, () => new Array(n).fill(Infinity));

// 枚举所有子串
for (let l = 0; l < n; l++) {
for (let r = l; r < n; r++) {
const len = r - l + 1;
if (len < 2) continue; // 长度至少为2才能成为半回文

let best = Infinity;
// 枚举 len 的所有真因子 d (1 <= d < len 且 len % d == 0)
// 注意:d = len 排除,因为要求真因子
for (let d = 1; d < len; d++) {
if (len % d !== 0) continue;

let changes = 0;
// 对于每个起始偏移 start (0 ~ d-1)
for (let start = 0; start < d; start++) {
// 收集该轨的所有下标: start, start+d, start+2d, ... 且 <= r
// 使用双指针从两端往中间配对
let i = l + start;
let j = l + start + Math.floor((len - 1 - start) / d) * d;
while (i < j) {
if (s[i] !== s[j]) changes++;
i += d;
j -= d;
}
}
best = Math.min(best, changes);
}
cost[l][r] = best;
}
}

// ---------- 2. 动态规划:将字符串分成 k 段 ----------
// dp[i][c] : 将前 i 个字符 (s[0..i-1]) 分成 c 段的最少修改次数
const dp = Array.from({ length: n + 1 }, () => new Array(k + 1).fill(Infinity));
dp[0][0] = 0; // 空串分成0段代价为0

for (let c = 1; c <= k; c++) { // 枚举段数
for (let i = c; i <= n; i++) { // 至少需要 c 个字符
// 枚举最后一段的起始位置 j (前 j 个字符已经分成了 c-1 段)
for (let j = c - 1; j < i; j++) {
if (dp[j][c - 1] !== Infinity && cost[j][i - 1] !== Infinity) {
dp[i][c] = Math.min(dp[i][c], dp[j][c - 1] + cost[j][i - 1]);
}
}
}
}

return dp[n][k];
};
```

解题核心要点

1. 半回文的判断与最小修改

· 定义:长度为 L 的字符串,若存在一个真因子 d(1 ≤ d < L 且 d | L),使得将所有下标按 mod d 分组后,每组都是一个回文串,则称该字符串是半回文。
· 修改次数计算:
· 对于每个 d,将原串映射成 d 条轨道。
· 每条轨道内的字符必须回文,所以统计轨道内对称位置不同的对数。
· 所有轨道的总不同对数就是按此 d 划分需要的修改次数。
· 取所有真因子 d 中的最小值。

2. 为什么预处理是必要的?

如果直接在 DP 里对每个子串实时计算半回文代价,会重复计算大量子串,复杂度爆炸。预处理 O(n^3) 在 n ≤ 200 时可行(约 800 万次操作),DP 额外 O(n^2 * k) 也 ok。

3. 动态规划细节

· 状态 dp[i][c]:前 i 个字符分成 c 段的最小代价。
· 转移:枚举最后一段的开始位置 j(前 j 个字符分成 c-1 段),加上子串 s[j..i-1] 的半回文代价。
· 初始化 dp[0][0] = 0,其他为无穷大。
· 答案 = dp[n][k]。

4. 边界条件

· 若 k > n 理论上无法划分(题目不会出现)。
· 若某个子串无法变成半回文(如长度 1),则 cost 保持 Infinity,DP 时自动跳过。

复杂度

· 时间:预处理 O(n^3),DP O(n^2 * k),实测 n=200 时能通过。
· 空间:O(n^2) 存储 cost,加上 DP 表 O(n*k)。

测试样例

```javascript
console.log(minimumChanges("abcde", 2)); // 输出:?
console.log(minimumChanges("aabbaa", 3)); // 输出:?
```

你可以自行运行验证。

如果你需要进一步了解如何优化因子的枚举或提前剪枝,或者对半回文定义有疑问,欢迎继续提问。

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

相关文章:

  • Bash 专业人员笔记 -- 第 28 章:进程替换
  • DRC设计规则检查
  • 手把手教你:如何将HAL库项目从STM32F103RCT6无缝迁移到C8T6(附源码下载)
  • 第130期《Installer》推荐:多款新品、屏幕分享、读者好物及Spotify实用功能!
  • 中文文本分类完整训练工程:PyTorch+BERT实现CPWS与CNews数据集端到端跑通
  • UE5 GAS实战:手把手教你为RPG角色创建第一个AttributeSet(含Health/Mana完整代码)
  • GSEA分析避坑指南:从NES、FDR到leading edge,这些参数设置错了结果全白费
  • Paza项目:低资源语言语音识别的社区驱动范式与实战指南
  • Sora 2字幕添加实操手册:5种兼容格式+4类常见报错修复+1键同步时间轴(附官方API调用验证数据)
  • Unity新手必看:用Animation和Trigger做个能捡钥匙开的门(附完整代码)
  • 雷达信号处理入门:LFM调频连续波如何实现‘看得更清’?
  • Contextual Bandit:从理论到实践,构建深度个性化推荐系统
  • C#后台导入Excel别再写复杂解析了!MiniExcel一行代码映射到实体类(含表头不对齐的解决方案)
  • 保姆级教程:用PX4和ROS在Gazebo仿真中实现无人机自动画圆(附完整代码与脚本)
  • 从高频交易到Kaggle Grandmaster:跨领域思维如何塑造顶尖数据科学家
  • MATLAB行人检测实战包:HOG特征提取+滑动窗口+SVM分类全流程代码
  • 企业级网络运维接入LLM大模型(在线)实战
  • API即服务:微创业者的技术新基建与实战指南
  • FortiGate新老版本分流方案对比:手动建IP组 vs 一键调用地理数据库,哪个更适合你?
  • Visual Studio 科研工作流:集成 Jupyter、Git LFS 与 MLflow 实现高效研究
  • OpenAI 5个月生成百万行代码!揭秘AI工程师的进化之路:Prompt、Context、Harness工程
  • 微软EMEA奖学金计划:AI产学研协作模式解析与盲童社交技能辅助案例
  • ECharts 5.4.3版本避坑:手把手教你实现‘悬浮’引导线的3D环状饼图
  • 避坑指南:mmsegmentation自定义数据集时,90%新手会遇到的3个报错及解决方法
  • 你的第一个双轮差速小车底盘:Arduino Mega2560核心,TB6612驱动MG513电机全攻略(附完整代码库)
  • 企业安全产品失效真相:仪表盘谎言与责任鸿沟的深度剖析
  • KMS智能激活工具:Windows和Office永久激活的终极完整指南
  • PyInstaller打包PaddleOCR项目,RuntimeError: PreconditionNotMet报错?手把手教你补全缺失的DLL和依赖包
  • TranslucentTB启动失败:Microsoft.UI.Xaml框架依赖问题的终极解决方案
  • 告别手动计算!用Arcmap的栅格计算器,5分钟搞定MK-sen与Hurst结果的趋势叠置分析