如何计算基于Trie实现的LeetCode最长公共前缀解法的时间复杂度
我先梳理你的解法核心逻辑:通过构建Trie树统计每个节点的访问次数,找到所有字符串都经过的节点路径作为候选前缀,再筛选最长的那个。接下来纠正你时间复杂度分析里的不准确之处:
你的分析错误点纠正
第88行(
FindAllPrefixes方法):
你认为是O(nm),这里的定义混乱了。实际这个方法里,每个字符串最多遍历到其最长公共前缀的长度(设为L,即最终结果的长度,最长不超过所有字符串中最短的长度),而非整个字符串长度。所以这部分的时间复杂度应该是**O(mL)**,其中m是字符串数组的元素个数,L是最长公共前缀的长度。第89行(排序前缀列表):
你用了O(kj),但k其实就是m(每个字符串对应一个候选前缀),排序时比较的是前缀长度,每个比较操作是O(1)(仅比较长度数值),所以排序的时间复杂度是O(m log m),而非O(kj)。排序的时间主要由元素个数决定,字符串长度比较是常数时间。总复杂度计算错误:
你错误地把各部分复杂度相乘了,但时间复杂度是取各部分的最大值(或相加后取主导项)。另外你遗漏了构建Trie的时间(第85-86行):构建Trie需要遍历所有字符串的每个字符,总共有S个字符(S是所有字符串的总长度),所以这部分是O(S)。
正确的总时间复杂度
把各部分的复杂度列出来:
- 构建Trie:O(S),S是所有字符串的总字符数
- 生成候选前缀列表:O(m*L),m是字符串数量,L是最长公共前缀长度
- 排序前缀列表:O(m log m)
- 验证前缀:O(m*L)
总复杂度由最大的项主导,所以最终时间复杂度是O(S + m log m)。因为S通常远大于m*L和m log m(比如当字符串很长但公共前缀很短时);如果所有字符串完全相同,S = m*L,此时总复杂度为O(m*L + m log m),依然可简化为O(S + m log m)。
另外补充:你的解法存在优化空间,比如FindAllPrefixes后不需要排序再验证——最长公共前缀是唯一的,你可以直接在Trie遍历过程中一次找到最长公共前缀,去掉O(m log m)的排序开销,进一步提升性能。
格式化后的代码
public class Solution { public class TrieNode { public bool IsEndOfString = false; public TrieNode[]? Children; public int Visits = 0; } public class Trie { private const int AlphabetSize = 26; private TrieNode _root; private int _max = 0; public Trie() { _root = new TrieNode() { Visits = -1, Children = new TrieNode[AlphabetSize] }; } public void Insert(string key) { TrieNode currentNode = _root; foreach (char c in key) { int currentIndex = c - 'a'; if (currentNode.Children[currentIndex] is null) currentNode.Children[currentIndex] = new TrieNode() { Children = new TrieNode[AlphabetSize] }; currentNode = currentNode.Children[currentIndex]; currentNode.Visits++; if (currentNode.Visits > _max) _max = currentNode.Visits; } currentNode.IsEndOfString = true; } public List<string> FindAllPrefixes(string[] strs) { StringBuilder prefixBuilder = new StringBuilder(); List<string> listOfPrefixes = new List<string>(); foreach (string key in strs) { TrieNode currentNode = _root; foreach (char c in key) { var currentIndex = c - 'a'; if (currentNode.Children[currentIndex] is not null && currentNode.Children[currentIndex].Visits == _max) { prefixBuilder.Append((char) (currentIndex + 'a')); currentNode = currentNode.Children[currentIndex]; } else break; } listOfPrefixes.Add(prefixBuilder.ToString()); prefixBuilder.Clear(); } return listOfPrefixes; } } public string LongestCommonPrefix(string[] strs) { if (strs.Length == 1) return strs[0]; Trie trie = new Trie(); foreach (string str in strs) trie.Insert(str); List<string> listOfPrefixes = trie.FindAllPrefixes(strs); listOfPrefixes.Sort((a, b) => b.Length.CompareTo(a.Length)); string longestPrefix = listOfPrefixes[0]; foreach (string str in strs) { if (!str.StartsWith(longestPrefix)) return string.Empty; } return longestPrefix; } }
内容的提问来源于stack exchange,提问作者iamhaitham

