Off-by-one on range boundaries
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.
Break down a hard problem into reliable checkpoints, edge-case handling, and complexity trade-offs.
You are given an array of strings words and an integer k.
For each index i in the range [0, words.length - 1], find the length of the longest common prefix among any k strings (selected at distinct indices) from the remaining array after removing the ith element.
Return an array answer, where answer[i] is the answer for ith element. If removing the ith element leaves the array with fewer than k strings, answer[i] is 0.
Example 1:
Input: words = ["jump","run","run","jump","run"], k = 2
Output: [3,4,4,3,4]
Explanation:
"jump"):
words becomes: ["run", "run", "jump", "run"]. "run" occurs 3 times. Choosing any two gives the longest common prefix "run" (length 3)."run"):
words becomes: ["jump", "run", "jump", "run"]. "jump" occurs twice. Choosing these two gives the longest common prefix "jump" (length 4)."run"):
words becomes: ["jump", "run", "jump", "run"]. "jump" occurs twice. Choosing these two gives the longest common prefix "jump" (length 4)."jump"):
words becomes: ["jump", "run", "run", "run"]. "run" occurs 3 times. Choosing any two gives the longest common prefix "run" (length 3).words becomes: ["jump", "run", "run", "jump"]. "jump" occurs twice. Choosing these two gives the longest common prefix "jump" (length 4).Example 2:
Input: words = ["dog","racer","car"], k = 2
Output: [0,0,0]
Explanation:
Constraints:
1 <= k <= words.length <= 1051 <= words[i].length <= 104words[i] consists of lowercase English letters.words[i].length is smaller than or equal 105.Problem summary: You are given an array of strings words and an integer k. For each index i in the range [0, words.length - 1], find the length of the longest common prefix among any k strings (selected at distinct indices) from the remaining array after removing the ith element. Return an array answer, where answer[i] is the answer for ith element. If removing the ith element leaves the array with fewer than k strings, answer[i] is 0.
Start with the most direct exhaustive search. That gives a correctness anchor before optimizing.
Pattern signal: Array · Trie
["jump","run","run","jump","run"] 2
["dog","racer","car"] 2
Source-backed implementations are provided below for direct study and interview prep.
// Accepted solution for LeetCode #3485: Longest Common Prefix of K Strings After Removal
class Solution {
static class TrieNode {
int count = 0;
int depth = 0;
int[] children = new int[26];
TrieNode() {
for (int i = 0; i < 26; ++i) children[i] = -1;
}
}
static 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)];
for (int i = 0; i < tree.length; i++) tree[i] = -1;
build(1, 1, n);
}
void build(int idx, int l, int r) {
if (l == r) {
tree[idx] = globalCount[l] > 0 ? l : -1;
return;
}
int mid = (l + r) / 2;
build(idx * 2, l, mid);
build(idx * 2 + 1, mid + 1, r);
tree[idx] = Math.max(tree[idx * 2], tree[idx * 2 + 1]);
}
void update(int idx, int l, int r, int pos, int newVal) {
if (l == r) {
tree[idx] = newVal > 0 ? l : -1;
return;
}
int mid = (l + r) / 2;
if (pos <= mid) {
update(idx * 2, l, mid, pos, newVal);
} else {
update(idx * 2 + 1, mid + 1, r, pos, newVal);
}
tree[idx] = Math.max(tree[idx * 2], tree[idx * 2 + 1]);
}
int query() {
return tree[1];
}
}
public int[] longestCommonPrefix(String[] words, int k) {
int n = words.length;
int[] ans = new int[n];
if (n - 1 < k) return ans;
ArrayList<TrieNode> trie = new ArrayList<>();
trie.add(new TrieNode());
for (String word : words) {
int cur = 0;
for (char c : word.toCharArray()) {
int idx = c - 'a';
if (trie.get(cur).children[idx] == -1) {
trie.get(cur).children[idx] = trie.size();
TrieNode node = new TrieNode();
node.depth = trie.get(cur).depth + 1;
trie.add(node);
}
cur = trie.get(cur).children[idx];
trie.get(cur).count++;
}
}
int maxDepth = 0;
for (int i = 1; i < trie.size(); ++i) {
if (trie.get(i).count >= k) {
maxDepth = Math.max(maxDepth, trie.get(i).depth);
}
}
int[] globalCount = new int[maxDepth + 1];
for (int i = 1; i < trie.size(); ++i) {
TrieNode node = trie.get(i);
if (node.count >= k && node.depth <= maxDepth) {
globalCount[node.depth]++;
}
}
List<List<Integer>> fragileList = new ArrayList<>();
for (int i = 0; i < n; ++i) {
fragileList.add(new ArrayList<>());
}
for (int i = 0; i < n; ++i) {
int cur = 0;
for (char c : words[i].toCharArray()) {
int idx = c - 'a';
cur = trie.get(cur).children[idx];
if (trie.get(cur).count == k) {
fragileList.get(i).add(trie.get(cur).depth);
}
}
}
int segSize = maxDepth;
if (segSize >= 1) {
SegmentTree segTree = new SegmentTree(segSize, globalCount);
for (int i = 0; i < n; ++i) {
if (n - 1 < k) {
ans[i] = 0;
} else {
for (int d : fragileList.get(i)) {
segTree.update(1, 1, segSize, d, globalCount[d] - 1);
}
int res = segTree.query();
ans[i] = res == -1 ? 0 : res;
for (int d : fragileList.get(i)) {
segTree.update(1, 1, segSize, d, globalCount[d]);
}
}
}
}
return ans;
}
}
// Accepted solution for LeetCode #3485: Longest Common Prefix of K Strings After Removal
package main
import (
"cmp"
"slices"
)
// https://space.bilibili.com/206214
// 计算 s 和 t 的最长公共前缀(LCP)长度
func calcLCP(s, t string) int {
n := min(len(s), len(t))
for i := range n {
if s[i] != t[i] {
return i
}
}
return n
}
func longestCommonPrefix(words []string, k int) []int {
n := len(words)
if k >= n { // 移除一个字符串后,剩余字符串少于 k 个
return make([]int, n)
}
idx := make([]int, n)
for i := range idx {
idx[i] = i
}
slices.SortFunc(idx, func(i, j int) int { return cmp.Compare(words[i], words[j]) })
// 计算最大 LCP 长度和次大 LCP 长度,同时记录最大 LCP 来自哪里
mx, mx2, mxI := -1, -1, -1
for i := range n - k + 1 {
// 排序后,[i, i+k-1] 的 LCP 等于两端点的 LCP
lcp := calcLCP(words[idx[i]], words[idx[i+k-1]])
if lcp > mx {
mx, mx2, mxI = lcp, mx, i
} else if lcp > mx2 {
mx2 = lcp
}
}
ans := make([]int, n)
for i := range ans {
ans[i] = mx // 先初始化成最大 LCP 长度
}
// 移除下标在 [mxI, mxI+k-1] 中的字符串,会导致最大 LCP 变成次大 LCP
for _, i := range idx[mxI : mxI+k] {
ans[i] = mx2 // 改成次大 LCP 长度
}
return ans
}
# Accepted solution for LeetCode #3485: Longest Common Prefix of K Strings After Removal
class TrieNode:
def __init__(self):
self.children: dict[str, TrieNode] = {}
self.count = 0
class Trie:
def __init__(self, k: int):
self.k = k
self.root = TrieNode()
self.prefixLengthsCount = collections.Counter()
self.prefixLengths = SortedList()
def insert(self, word: str) -> None:
node = self.root
for i, c in enumerate(word):
sz = i + 1
node = node.children.setdefault(c, TrieNode())
node.count += 1
if node.count >= self.k:
self.prefixLengthsCount[sz] += 1
if self.prefixLengthsCount[sz] == 1:
self.prefixLengths.add(-sz)
def erase(self, word: str) -> None:
node = self.root
for i, c in enumerate(word):
sz = i + 1
node = node.children[c]
if node.count == self.k:
self.prefixLengthsCount[sz] -= 1
if self.prefixLengthsCount[sz] == 0:
self.prefixLengths.remove(-sz)
node.count -= 1
def getLongestCommonPrefix(self) -> int:
return 0 if not self.prefixLengths else -self.prefixLengths[0]
class Solution:
def longestCommonPrefix(self, words: list[str], k: int) -> list[int]:
ans = []
trie = Trie(k)
for word in words:
trie.insert(word)
for word in words:
trie.erase(word)
ans.append(trie.getLongestCommonPrefix())
trie.insert(word)
return ans
// Accepted solution for LeetCode #3485: Longest Common Prefix of K Strings After Removal
// Rust example auto-generated from java reference.
// Replace the signature and local types with the exact LeetCode harness for this problem.
impl Solution {
pub fn rust_example() {
// Port the logic from the reference block below.
}
}
// Reference (java):
// // Accepted solution for LeetCode #3485: Longest Common Prefix of K Strings After Removal
// class Solution {
// static class TrieNode {
// int count = 0;
// int depth = 0;
// int[] children = new int[26];
//
// TrieNode() {
// for (int i = 0; i < 26; ++i) children[i] = -1;
// }
// }
//
// static 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)];
// for (int i = 0; i < tree.length; i++) tree[i] = -1;
// build(1, 1, n);
// }
//
// void build(int idx, int l, int r) {
// if (l == r) {
// tree[idx] = globalCount[l] > 0 ? l : -1;
// return;
// }
// int mid = (l + r) / 2;
// build(idx * 2, l, mid);
// build(idx * 2 + 1, mid + 1, r);
// tree[idx] = Math.max(tree[idx * 2], tree[idx * 2 + 1]);
// }
//
// void update(int idx, int l, int r, int pos, int newVal) {
// if (l == r) {
// tree[idx] = newVal > 0 ? l : -1;
// return;
// }
// int mid = (l + r) / 2;
// if (pos <= mid) {
// update(idx * 2, l, mid, pos, newVal);
// } else {
// update(idx * 2 + 1, mid + 1, r, pos, newVal);
// }
// tree[idx] = Math.max(tree[idx * 2], tree[idx * 2 + 1]);
// }
//
// int query() {
// return tree[1];
// }
// }
//
// public int[] longestCommonPrefix(String[] words, int k) {
// int n = words.length;
// int[] ans = new int[n];
// if (n - 1 < k) return ans;
//
// ArrayList<TrieNode> trie = new ArrayList<>();
// trie.add(new TrieNode());
//
// for (String word : words) {
// int cur = 0;
// for (char c : word.toCharArray()) {
// int idx = c - 'a';
// if (trie.get(cur).children[idx] == -1) {
// trie.get(cur).children[idx] = trie.size();
// TrieNode node = new TrieNode();
// node.depth = trie.get(cur).depth + 1;
// trie.add(node);
// }
// cur = trie.get(cur).children[idx];
// trie.get(cur).count++;
// }
// }
//
// int maxDepth = 0;
// for (int i = 1; i < trie.size(); ++i) {
// if (trie.get(i).count >= k) {
// maxDepth = Math.max(maxDepth, trie.get(i).depth);
// }
// }
//
// int[] globalCount = new int[maxDepth + 1];
// for (int i = 1; i < trie.size(); ++i) {
// TrieNode node = trie.get(i);
// if (node.count >= k && node.depth <= maxDepth) {
// globalCount[node.depth]++;
// }
// }
//
// List<List<Integer>> fragileList = new ArrayList<>();
// for (int i = 0; i < n; ++i) {
// fragileList.add(new ArrayList<>());
// }
//
// for (int i = 0; i < n; ++i) {
// int cur = 0;
// for (char c : words[i].toCharArray()) {
// int idx = c - 'a';
// cur = trie.get(cur).children[idx];
// if (trie.get(cur).count == k) {
// fragileList.get(i).add(trie.get(cur).depth);
// }
// }
// }
//
// int segSize = maxDepth;
// if (segSize >= 1) {
// SegmentTree segTree = new SegmentTree(segSize, globalCount);
// for (int i = 0; i < n; ++i) {
// if (n - 1 < k) {
// ans[i] = 0;
// } else {
// for (int d : fragileList.get(i)) {
// segTree.update(1, 1, segSize, d, globalCount[d] - 1);
// }
// int res = segTree.query();
// ans[i] = res == -1 ? 0 : res;
// for (int d : fragileList.get(i)) {
// segTree.update(1, 1, segSize, d, globalCount[d]);
// }
// }
// }
// }
//
// return ans;
// }
// }
// Accepted solution for LeetCode #3485: Longest Common Prefix of K Strings After Removal
// Auto-generated TypeScript example from java.
function exampleSolution(): void {
}
// Reference (java):
// // Accepted solution for LeetCode #3485: Longest Common Prefix of K Strings After Removal
// class Solution {
// static class TrieNode {
// int count = 0;
// int depth = 0;
// int[] children = new int[26];
//
// TrieNode() {
// for (int i = 0; i < 26; ++i) children[i] = -1;
// }
// }
//
// static 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)];
// for (int i = 0; i < tree.length; i++) tree[i] = -1;
// build(1, 1, n);
// }
//
// void build(int idx, int l, int r) {
// if (l == r) {
// tree[idx] = globalCount[l] > 0 ? l : -1;
// return;
// }
// int mid = (l + r) / 2;
// build(idx * 2, l, mid);
// build(idx * 2 + 1, mid + 1, r);
// tree[idx] = Math.max(tree[idx * 2], tree[idx * 2 + 1]);
// }
//
// void update(int idx, int l, int r, int pos, int newVal) {
// if (l == r) {
// tree[idx] = newVal > 0 ? l : -1;
// return;
// }
// int mid = (l + r) / 2;
// if (pos <= mid) {
// update(idx * 2, l, mid, pos, newVal);
// } else {
// update(idx * 2 + 1, mid + 1, r, pos, newVal);
// }
// tree[idx] = Math.max(tree[idx * 2], tree[idx * 2 + 1]);
// }
//
// int query() {
// return tree[1];
// }
// }
//
// public int[] longestCommonPrefix(String[] words, int k) {
// int n = words.length;
// int[] ans = new int[n];
// if (n - 1 < k) return ans;
//
// ArrayList<TrieNode> trie = new ArrayList<>();
// trie.add(new TrieNode());
//
// for (String word : words) {
// int cur = 0;
// for (char c : word.toCharArray()) {
// int idx = c - 'a';
// if (trie.get(cur).children[idx] == -1) {
// trie.get(cur).children[idx] = trie.size();
// TrieNode node = new TrieNode();
// node.depth = trie.get(cur).depth + 1;
// trie.add(node);
// }
// cur = trie.get(cur).children[idx];
// trie.get(cur).count++;
// }
// }
//
// int maxDepth = 0;
// for (int i = 1; i < trie.size(); ++i) {
// if (trie.get(i).count >= k) {
// maxDepth = Math.max(maxDepth, trie.get(i).depth);
// }
// }
//
// int[] globalCount = new int[maxDepth + 1];
// for (int i = 1; i < trie.size(); ++i) {
// TrieNode node = trie.get(i);
// if (node.count >= k && node.depth <= maxDepth) {
// globalCount[node.depth]++;
// }
// }
//
// List<List<Integer>> fragileList = new ArrayList<>();
// for (int i = 0; i < n; ++i) {
// fragileList.add(new ArrayList<>());
// }
//
// for (int i = 0; i < n; ++i) {
// int cur = 0;
// for (char c : words[i].toCharArray()) {
// int idx = c - 'a';
// cur = trie.get(cur).children[idx];
// if (trie.get(cur).count == k) {
// fragileList.get(i).add(trie.get(cur).depth);
// }
// }
// }
//
// int segSize = maxDepth;
// if (segSize >= 1) {
// SegmentTree segTree = new SegmentTree(segSize, globalCount);
// for (int i = 0; i < n; ++i) {
// if (n - 1 < k) {
// ans[i] = 0;
// } else {
// for (int d : fragileList.get(i)) {
// segTree.update(1, 1, segSize, d, globalCount[d] - 1);
// }
// int res = segTree.query();
// ans[i] = res == -1 ? 0 : res;
// for (int d : fragileList.get(i)) {
// segTree.update(1, 1, segSize, d, globalCount[d]);
// }
// }
// }
// }
//
// return ans;
// }
// }
Use this to step through a reusable interview workflow for this problem.
Store all N words in a hash set. Each insert/lookup hashes the entire word of length L, giving O(L) per operation. Prefix queries require checking every stored word against the prefix — O(N × L) per prefix search. Space is O(N × L) for storing all characters.
Each operation (insert, search, prefix) takes O(L) time where L is the word length — one node visited per character. Total space is bounded by the sum of all stored word lengths. Tries win over hash sets when you need prefix matching: O(L) prefix search vs. checking every stored word.
Review these before coding to avoid predictable interview regressions.
Wrong move: Loop endpoints miss first/last candidate.
Usually fails on: Fails on minimal arrays and exact-boundary answers.
Fix: Re-derive loops from inclusive/exclusive ranges before coding.