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

Kimi LeetCode 3569. 分割数组后不同质数的最大数目 JavaScript实现

以下是 LeetCode 3569「分割数组后不同质数的最大数目」的 JavaScript 实现。

核心思路

将数组在位置 `k`(`1 ≤ k < n`)处分割为左右两部分。对于某个质数 `p`:

- 若 `p` 在数组中只出现 1 次:无论怎么分割,它只能出现在一边,贡献 1。
- 若 `p` 出现 ≥ 2 次(首次在 `l`,末次在 `r`):
- 当分割点 `k ∈ [l+1, r]` 时,`p` 会同时出现在左右两边,贡献 2(额外贡献 +1)。
- 否则贡献 1。

因此,每次查询的答案为:

> 不同质数的总数 + 最大额外贡献

其中「最大额外贡献」等价于:在区间 `[1, n-1]` 中,有多少个质数满足 `k ∈ [l+1, r]`。这需要用懒标记线段树维护区间加、区间最大值,以支持 `O(log n)` 的单点修改。

代码实现

```javascript
/**
* @param {number[]} nums
* @param {number[][]} queries
* @return {number[]}
*/
var maximumCount = function(nums, queries) {
const MX = 100000;
const n = nums.length;

// 筛法预处理质数
const isPrime = new Array(MX + 1).fill(true);
isPrime[0] = isPrime[1] = false;
for (let i = 2; i * i <= MX; i++) {
if (isPrime[i]) {
for (let j = i * i; j <= MX; j += i) {
isPrime[j] = false;
}
}
}

// 懒标记线段树:区间加、区间最大值
class LazySegTree {
constructor(size, initVal) {
this.n = size;
this.mx = new Array(4 * size).fill(0);
this.lazy = new Array(4 * size).fill(0);
this._build(1, 0, size - 1, initVal);
}

_build(node, l, r, initVal) {
if (l === r) {
this.mx[node] = initVal;
return;
}
const mid = (l + r) >> 1;
this._build(node << 1, l, mid, initVal);
this._build(node << 1 | 1, mid + 1, r, initVal);
this.mx[node] = Math.max(this.mx[node << 1], this.mx[node << 1 | 1]);
}

_apply(node, val) {
this.mx[node] += val;
this.lazy[node] += val;
}

_push(node) {
const val = this.lazy[node];
if (val !== 0) {
this._apply(node << 1, val);
this._apply(node << 1 | 1, val);
this.lazy[node] = 0;
}
}

_update(node, l, r, ql, qr, val) {
if (ql <= l && r <= qr) {
this._apply(node, val);
return;
}
this._push(node);
const mid = (l + r) >> 1;
if (ql <= mid) this._update(node << 1, l, mid, ql, qr, val);
if (qr > mid) this._update(node << 1 | 1, mid + 1, r, ql, qr, val);
this.mx[node] = Math.max(this.mx[node << 1], this.mx[node << 1 | 1]);
}

update(l, r, val) {
if (l > r) return;
this._update(1, 0, this.n - 1, l, r, val);
}

query() {
return this.mx[1];
}
}

// pos[p] = 质数 p 的所有出现位置(有序数组)
const pos = new Map();
for (let i = 0; i < n; i++) {
const x = nums[i];
if (isPrime[x]) {
if (!pos.has(x)) pos.set(x, []);
pos.get(x).push(i);
}
}

// 初始化线段树:对出现 ≥ 2 次的质数,在 [l+1, r] 区间 +1
const seg = new LazySegTree(n, 0);
for (const arr of pos.values()) {
if (arr.length >= 2) {
const l = arr[0];
const r = arr[arr.length - 1];
const left = Math.max(l + 1, 1);
const right = Math.min(r, n - 1);
if (left <= right) seg.update(left, right, 1);
}
}

const ans = [];

// 从质数 p 的位置数组中删除索引 idx
function removeFromPrime(p, idx) {
const arr = pos.get(p);
const len = arr.length;

if (len === 1) {
pos.delete(p);
return;
}

const l = arr[0];
const r = arr[len - 1];

let posIdx = -1;
for (let i = 0; i < len; i++) {
if (arr[i] === idx) { posIdx = i; break; }
}
if (posIdx === -1) return;

arr.splice(posIdx, 1);

if (len === 2) {
// 从 2 个变 1 个,消除该质数的额外贡献
const left = Math.max(l + 1, 1);
const right = Math.min(r, n - 1);
if (left <= right) seg.update(left, right, -1);
} else {
const newL = arr[0];
const newR = arr[arr.length - 1];
if (idx === l) {
// 删除了最左端,范围从左边收缩
const left = Math.max(l + 1, 1);
const right = Math.min(newL, n - 1);
if (left <= right) seg.update(left, right, -1);
} else if (idx === r) {
// 删除了最右端,范围从右边收缩
const left = Math.max(newR + 1, 1);
const right = Math.min(r, n - 1);
if (left <= right) seg.update(left, right, -1);
}
// 若删除的是中间位置,[l, r] 不变,无需更新
}
}

// 向质数 p 的位置数组中添加索引 idx
function addToPrime(p, idx) {
if (!pos.has(p)) {
pos.set(p, [idx]);
return;
}

const arr = pos.get(p);
const len = arr.length;
const l = arr[0];
const r = arr[len - 1];

let insertPos = 0;
while (insertPos < len && arr[insertPos] < idx) insertPos++;
arr.splice(insertPos, 0, idx);

if (len === 1) {
// 从 1 个变 2 个,新增额外贡献
const newL = arr[0];
const newR = arr[1];
const left = Math.max(newL + 1, 1);
const right = Math.min(newR, n - 1);
if (left <= right) seg.update(left, right, 1);
} else {
const newL = arr[0];
const newR = arr[arr.length - 1];
if (idx < l) {
// 向左扩展
const left = Math.max(idx + 1, 1);
const right = Math.min(l, n - 1);
if (left <= right) seg.update(left, right, 1);
} else if (idx > r) {
// 向右扩展
const left = Math.max(r + 1, 1);
const right = Math.min(idx, n - 1);
if (left <= right) seg.update(left, right, 1);
}
// 若插入到中间,[l, r] 不变,无需更新
}
}

// 处理每个查询
for (const q of queries) {
const idx = q[0];
const val = q[1];
const old = nums[idx];
nums[idx] = val;

if (isPrime[old]) removeFromPrime(old, idx);
if (isPrime[val]) addToPrime(val, idx);

ans.push(pos.size + seg.query());
}

return ans;
};
```

复杂度分析

- 时间复杂度:`O((n + q) · log n)`,其中 `q` 为查询次数。每次单点修改涉及线段树的 `O(log n)` 更新。
- 空间复杂度:`O(n)`,用于线段树、筛法数组和质数位置映射。

http://www.jsqmd.com/news/1190642/

相关文章:

  • 视频宣群技术指南:从制作到分发的完整实践方案
  • 数字IC丨后端设计到底“难”在哪?
  • 华为Sound X5智能音箱评测:金标音质与超级终端互联体验
  • 飘柔甜美花漾香氛洗发露测评:72小时留香与柔顺技术解析
  • 毕业用安卓商城APP源码:含支付宝支付、JSP后台、Android Studio一键运行
  • 剪映专业版教程:制作美女打太极动画视频(太极佳人出招)
  • 万国中国官方售后服务中心|地址与售后服务电话权威信息通知(2026年7月最新) - 万国中国官方服务中心
  • 今生AI命理报告带你揭秘藏在数字里的人生运势密码 - 优企甄选
  • 3个维度深度解析:如何用GBFR-Logs提升《碧蓝幻想:Relink》战斗表现
  • 串口通信协议详解—从电平标准到数据帧的实战解析
  • 模板驱动型文档自动化:专业内容出版的流水线革命
  • Intel平台驱动开发:openEuler内核中的硬件支持与集成方案
  • Python BeautifulSoup电影数据爬取实战:从HTML到结构化数据
  • PUBG罗技鼠标宏压枪脚本:终极技术指南与实战配置
  • Chrome浏览器必备神器:三秒实现网页与手机的无缝接力
  • 终极Markdown编辑革命:为什么Obsidian Editing Toolbar让专业写作变得如此简单
  • Jupyter Lab:从模块化界面到高效工作流
  • 抖音无水印下载终极指南:5个实用技巧帮你快速批量保存高清视频
  • 天梭中国官方售后服务中心|服务热线及具体地址权威信息公告(2026年7月更新) - 天梭服务中心
  • 射频采样ADC架构解析与ADC08DJ3200实战设计指南
  • 通信原理 从零到一:QPSK调制解调系统MATLAB仿真与星座图分析实战
  • Python新手7个认知提示:重建编程思维操作系统
  • 【数字信号处理】从模拟到数字:深入解析正弦序列、角频率与采样定理的实战关联
  • Unity资源包导入与项目构建实战:从Chaos Ball解析完整游戏开发流程
  • 高校二手交易系统实战源码:Vue2+Spring Boot全栈实现,含完整部署文档与答辩材料
  • 终极FMA音乐分析数据集指南:如何快速上手音乐AI研究
  • 基于OpenCV与Qt的C++图像处理软件开发:从架构设计到工程实践
  • 数电交通灯控制器:从状态机设计到硬件实现
  • LangGraph工作流集成FastAPI实战:构建可生产AI应用
  • 头歌 软件测试项目综合实训:从JUnit入门到实战,构建Java单元测试知识体系