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

Kimi LeetCode 3485. 删除元素后 K 个字符串的最长公共前缀 Java实现

以下是 LeetCode 3485 删除元素后 K 个字符串的最长公共前缀 的 Java 实现。

思路

核心思路是 Trie + 线段树:

1. 构建 Trie:将所有字符串插入字典树,每个节点记录 `count`(经过该节点的字符串数)和 `depth`(前缀长度)。
2. 统计有效深度:对于每个深度 `d`,统计有多少个 Trie 节点的 `count >= k`。若该数量 `> 0`,则深度 `d` 是一个候选答案(存在至少 `k` 个字符串共享长度为 `d` 的前缀)。
3. 线段树维护:用线段树维护每个深度是否有效(有效则值为深度 `d`,否则为 `-1`),支持单点更新和全局最大值查询。
4. 删除模拟:对于每个单词,找到它在 Trie 路径上所有 `count == k` 的节点(称为 fragile 节点)。删除该单词时,这些节点的 `count` 会变为 `k-1`,不再满足条件。临时更新线段树后查询最大值,即可得到删除该单词后的答案,然后恢复。

时间复杂度 O(L · log D),其中 `L = Σ|words[i]| ≤ 1e5`,`D = maxDepth ≤ 1e5`。

---

Java 代码

```java
import java.util.*;

class Solution {
public int[] longestCommonPrefix(String[] words, int k) {
int n = words.length;
int[] ans = new int[n];

// 删除后剩余不足 k 个,直接全 0
if (n - 1 < k) {
return ans;
}

// ---------- 1. 构建 Trie ----------
TrieNode root = new TrieNode();
for (String word : words) {
TrieNode node = root;
for (char c : word.toCharArray()) {
int idx = c - 'a';
if (node.children[idx] == null) {
node.children[idx] = new TrieNode();
node.children[idx].depth = node.depth + 1;
}
node = node.children[idx];
node.count++;
}
}

// ---------- 2. BFS 收集所有节点,求 maxDepth ----------
List<TrieNode> allNodes = new ArrayList<>();
Queue<TrieNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TrieNode node = queue.poll();
if (node != root) {
allNodes.add(node);
}
for (int i = 0; i < 26; i++) {
if (node.children[i] != null) {
queue.offer(node.children[i]);
}
}
}

int maxDepth = 0;
for (TrieNode node : allNodes) {
if (node.count >= k) {
maxDepth = Math.max(maxDepth, node.depth);
}
}

// 没有任何前缀被 >= k 个字符串共享
if (maxDepth == 0) {
return ans;
}

// globalCount[d] = 深度 d 上 count >= k 的节点数量
int[] globalCount = new int[maxDepth + 1];
for (TrieNode node : allNodes) {
if (node.count >= k && node.depth <= maxDepth) {
globalCount[node.depth]++;
}
}

// ---------- 3. 找每个单词的 fragile depths ----------
// fragile: 路径上 count == k 的节点深度,删除后该节点会失效
List<Integer>[] fragile = new ArrayList[n];
for (int i = 0; i < n; i++) {
fragile[i] = new ArrayList<>();
TrieNode node = root;
for (char c : words[i].toCharArray()) {
node = node.children[c - 'a'];
if (node.count == k) {
fragile[i].add(node.depth);
}
}
}

// ---------- 4. 线段树维护每个深度是否有效 ----------
SegmentTree seg = new SegmentTree(maxDepth, globalCount);

// ---------- 5. 枚举删除每个单词 ----------
for (int i = 0; i < n; i++) {
// 临时删除:fragile 深度上的有效节点数减 1
for (int d : fragile[i]) {
seg.update(1, 1, maxDepth, d, globalCount[d] - 1);
}
int res = seg.query();
ans[i] = res == -1 ? 0 : res;
// 恢复
for (int d : fragile[i]) {
seg.update(1, 1, maxDepth, d, globalCount[d]);
}
}

return ans;
}

// Trie 节点
class TrieNode {
TrieNode[] children = new TrieNode[26];
int count = 0; // 经过该节点的字符串数
int depth = 0; // 该节点代表的前缀长度
}

// 线段树:维护每个深度是否有效,查询最大有效深度
class SegmentTree {
int n;
int[] tree;
int[] globalCount;

SegmentTree(int n, int[] globalCount) {
this.n = n;
this.globalCount = globalCount;
this.tree = new int[4 * (n + 1)];
build(1, 1, n);
}

void build(int node, int l, int r) {
if (l == r) {
tree[node] = globalCount[l] > 0 ? l : -1;
return;
}
int mid = (l + r) >> 1;
build(node << 1, l, mid);
build(node << 1 | 1, mid + 1, r);
tree[node] = Math.max(tree[node << 1], tree[node << 1 | 1]);
}

// 将位置 pos 的值更新为 val(val 为该深度上 count >= k 的节点数)
void update(int node, int l, int r, int pos, int val) {
if (l == r) {
tree[node] = val > 0 ? l : -1;
return;
}
int mid = (l + r) >> 1;
if (pos <= mid) {
update(node << 1, l, mid, pos, val);
} else {
update(node << 1 | 1, mid + 1, r, pos, val);
}
tree[node] = Math.max(tree[node << 1], tree[node << 1 | 1]);
}

// 查询全局最大有效深度
int query() {
return tree[1];
}
}
}
```

---

复杂度分析

指标 复杂度
时间 O(L · log D),其中 `L = Σ\|words[i]\| ≤ 1e5`,`D = maxDepth ≤ 1e5`
空间 O(L + D),Trie 节点数 + 线段树 + 辅助数组

该解法通过 Trie 精确追踪每个前缀的覆盖次数,配合线段树实现高效的删除-查询-恢复,是此题的标准最优解。

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

相关文章:

  • SV基础之连接设计和测试平台
  • OpenClaw专栏第三篇:环境搭建前的准备工作清单(保姆级避坑指南)
  • YOLO26 TensorBoard集成:可视化训练过程的完整配置与高级用法
  • Wave-ViT: Unifying Wavelet and Transformersfor Visual Representation Learning ——ECCV
  • 解决UE4SS在Palworld游戏中的崩溃问题:GUI控制台兼容性深度解析
  • 西天取经四人组(Liunx小测试)
  • 在上一篇文章NHibernate实践总结(一)中,主要罗列了自己的一部分实践总结,其中包括:
  • C++ Primer Plus 重读精讲 _ 名称空间与代码组织
  • Windows 7用户必备:让Blender 3.x+在旧系统上焕发新生的终极方案
  • 实验一:IP 地址规划、静态路由及 DHCP 配置
  • EldenRingSaveCopier:三步完成艾尔登法环存档迁移的终极解决方案
  • 计算机组成原理4
  • 如何下载沪深股票市场的行情数据,包括委托、成交、订单薄、沪深股票、etf、可转债、指数五档订单薄数据,买一到买五、卖一到卖五历史行情数据笔记
  • Claude Code 是什么
  • 5个常见泰拉瑞亚创作难题,TEdit地图编辑器如何帮你轻松解决?
  • 好久不在这里写文章了。先祝各位六一儿童节快乐!
  • 第一章 为什么90%的Camera项目,都死在第一步?
  • 基于YOLOv8的铁路障碍物检测:从数据准备到边缘部署全流程实践
  • MySQL 视图详解
  • 吾爱大佬最新出品,牛批Plus!
  • Jenkins与SonarQube集成实战:5步构建代码质量自动化分析体系
  • MediaPipe BlazePose 33关键点实战:Python+OpenCV 实现深蹲计数与角度可视化
  • Java程序设计(第3版)第四章——类加载
  • 计算机毕业设计之基于JSP绿色旅游生态园管理系统
  • 解锁B站评论区:如何用Python获取完整视频评论数据
  • 一文读懂服务器:撑起整个互联网的底层核心
  • IDEA集成Git
  • Terraform 状态文件深度解析:从原理到远程Backend的6大核心作用
  • pandas实战指南:从数据加载到业务分析的完整闭环
  • 掌握Universal Extractor 2:从500+格式到高级提取完全指南