Algorithm n字符串的最长公共前缀
给定n个最大长度为m的字符串。我们如何找到其中至少两个字符串共享的最长公共前缀 示例:['flower','flow','hello','fleet'] 回答:flAlgorithm n字符串的最长公共前缀,algorithm,Algorithm,给定n个最大长度为m的字符串。我们如何找到其中至少两个字符串共享的最长公共前缀 示例:['flower','flow','hello','fleet'] 回答:fl 我在考虑为所有字符串构建一个Trie,然后检查最深的节点(满足最长的),该节点分支到两个或更多的子字符串(满足公共性)。这需要O(n*m)的时间和空间。有没有更好的方法来实现这一点我会对它们进行排序,您可以在n lg n时间内完成。然后,任何带有公共前缀的字符串都将紧挨在一起。事实上,您应该能够保留当前正在查看的索引的指针,并向下搜
我在考虑为所有字符串构建一个Trie,然后检查最深的节点(满足最长的),该节点分支到两个或更多的子字符串(满足公共性)。这需要O(n*m)的时间和空间。有没有更好的方法来实现这一点我会对它们进行排序,您可以在
n lg n
时间内完成。然后,任何带有公共前缀的字符串都将紧挨在一起。事实上,您应该能够保留当前正在查看的索引的指针,并向下搜索以获得非常快速的计算。有一个O(|S|*n)
解决此问题的方法,使用。[n
是字符串数,S
是最长的字符串]
(1) put all strings in a trie
(2) do a DFS in the trie, until you find the first vertex with more than 1 "edge".
(3) the path from the root to the node you found at (2) is the longest common prefix.
没有可能更快的解决方案那么它[在大O符号方面],在最坏的情况下,所有字符串都是相同的-你需要阅读所有字符串才能知道它。作为一个与我的另一个答案完全不同的答案 通过一次传递,您可以根据每个字符串的第一个字母对其进行存储 通过另一次传递,您可以根据第二次传递对每个桶进行排序。(这被称为基数排序,即每次通过时
O(n*m)
,和O(n)
)这将为您提供一个2的基线前缀
您可以安全地从数据集中删除前缀不为2的任何元素
当p
接近m
时,您可以继续基数排序,删除没有共享前缀p
的元素
这将为您提供与trie方法相同的O(n*m)
时间,但总是比trie快,因为trie必须查看每个字符串中的每个字符(当它进入结构时),而这种方法只能保证每个字符串查看2个字符,此时它会剔除大部分数据集
最坏的情况仍然是每个字符串都是相同的,这就是为什么它使用相同的大O表示法,但在所有情况下都会更快,因为在任何“非最坏情况”中都有不需要访问的字符。为什么要使用trie(需要O(mn)时间和O(mn)空格,只需使用基本的蛮力方法。第一个循环,找到最短的字符串作为minStr,需要o(n)时间,第二个循环,逐个与此minStr进行比较,并保留一个表示minStr最右边索引的变量,此循环需要o(mn),其中m是所有字符串的最短长度。代码如下所示
public String longestCommonPrefix(String[] strs) {
if(strs.length==0) return "";
String minStr=strs[0];
for(int i=1;i<strs.length;i++){
if(strs[i].length()<minStr.length())
minStr=strs[i];
}
int end=minStr.length();
for(int i=0;i<strs.length;i++){
int j;
for( j=0;j<end;j++){
if(minStr.charAt(j)!=strs[i].charAt(j))
break;
}
if(j<end)
end=j;
}
return minStr.substring(0,end);
}
public String longestCommonPrefix(String[]strs){
如果(strs.length==0)返回“”;
字符串minStr=strs[0];
对于(int i=1;ipublic String longestCommonPrefix(String[]strs){
if(strs==null | | strs.length==0)
返回“”;
char[]c_list=strs[0].toCharArray();
int len=c_list.length;
int j=0;
对于(int i=1;i
桶排序(基数排序)corsiKa所描述的可以扩展,使得所有字符串最终都单独放置在一个bucket中,此时,这样一个单独字符串的LCP是已知的。此外,每个字符串的字符串也是已知的;它比LCP长一个。bucket sort实际上是后缀数组的构造,但只是部分如此未执行的ARISON(如corsiKa所述)确实表示未添加到后缀数组中的后缀字符串部分。最后,此方法不仅可以确定LCP和Shustring,还可以轻松找到字符串中不存在的子序列。因为世界显然在乞求Swift的答案,这是我的;)
Swift 3更新:
func longestCommonPrefix(strings:[String]) -> String {
var commonPrefix = ""
var indices = strings.map { $0.startIndex}
outerLoop:
while true {
var toMatch: Character = "_"
for (whichString, f) in strings.enumerated() {
let cursor = indices[whichString]
if cursor == f.endIndex { break outerLoop }
indices[whichString] = f.characters.index(after: cursor)
if whichString == 0 { toMatch = f[cursor] }
if toMatch != f[cursor] { break outerLoop }
}
commonPrefix.append(toMatch)
}
return commonPrefix
}
值得注意的是:
这在O^2或O(nx m)中运行,其中n是字符串数,m
是最短的一个的长度
它使用String.Index数据类型,因此处理字符
类型所表示的图形集
考虑到我首先需要编写的函数:
/// Takes an array of Strings representing file system objects absolute
/// paths and turn it into a new array with the minimum number of common
/// ancestors, possibly pushing the root of the tree as many level downwards
/// as necessary
///
/// In other words, we compute the longest common prefix and remove it
func reify(fullPaths:[String]) -> [String] {
let lcp = longestCommonPrefix(fullPaths)
return fullPaths.map {
return $0[lcp.endIndex ..< $0.endIndex]
}
}
也许是一个更直观的解决方案。将先前迭代中已找到的前缀作为输入字符串导入剩余的或下一个字符串输入。[[w1,w2],w3],w4]…依此类推]
,其中[]
应该是两个字符串的LCP
public String findPrefixBetweenTwo(String A, String B){
String ans = "";
for (int i = 0, j = 0; i < A.length() && j < B.length(); i++, j++){
if (A.charAt(i) != B.charAt(j)){
return i > 0 ? A.substring(0, i) : "";
}
}
// Either of the string is prefix of another one OR they are same.
return (A.length() > B.length()) ? B.substring(0, B.length()) : A.substring(0, A.length());
}
public String longestCommonPrefix(ArrayList<String> A) {
if (A.size() == 1) return A.get(0);
String prefix = A.get(0);
for (int i = 1; i < A.size(); i++){
prefix = findPrefixBetweenTwo(prefix, A.get(i)); // chain the earlier prefix
}
return prefix;
}
两个(字符串A、字符串B)之间的公共字符串findPrefixB{
字符串ans=“”;
对于(inti=0,j=0;i0?A。子字符串(0,i):“”;
}
}
//这两个字符串中有一个是另一个字符串的前缀,或者它们是相同的。
返回(A.length()>B.length())?B.substring(0,B.length()):A.substring(0,A.length());
}
公共字符串最长公共前缀(ArrayList A){
如果(A.size()==1)返回A.get(0);
字符串前缀=A.get(0);
对于(int i=1;i
@Mark我相信这个例子应该是flow
。根据建议解决方案的上下文判断,它只对至少2个通用,而不是对所有人通用。我同意OP的一些澄清是必要的。一个字符串可以不带“fl”开头。“hello”用来证明它可以是任何字符串,而在1个字符串中不需要任何字符使用其他前缀的公共前缀将使用nmlog(nm),因为在最坏的情况下进行比较需要O(m),实际上它使其O(m*nlog(n))
而不是O(nmlog(nm))
,因为
/// Takes an array of Strings representing file system objects absolute
/// paths and turn it into a new array with the minimum number of common
/// ancestors, possibly pushing the root of the tree as many level downwards
/// as necessary
///
/// In other words, we compute the longest common prefix and remove it
func reify(fullPaths:[String]) -> [String] {
let lcp = longestCommonPrefix(fullPaths)
return fullPaths.map {
return $0[lcp.endIndex ..< $0.endIndex]
}
}
func testReifySimple() {
let samplePaths:[String] = [
"/root/some/file"
, "/root/some/other/file"
, "/root/another/file"
, "/root/direct.file"
]
let expectedPaths:[String] = [
"some/file"
, "some/other/file"
, "another/file"
, "direct.file"
]
let reified = PathUtilities().reify(samplePaths)
for (index, expected) in expectedPaths.enumerate(){
XCTAssert(expected == reified[index], "failed match, \(expected) != \(reified[index])")
}
}
public String findPrefixBetweenTwo(String A, String B){
String ans = "";
for (int i = 0, j = 0; i < A.length() && j < B.length(); i++, j++){
if (A.charAt(i) != B.charAt(j)){
return i > 0 ? A.substring(0, i) : "";
}
}
// Either of the string is prefix of another one OR they are same.
return (A.length() > B.length()) ? B.substring(0, B.length()) : A.substring(0, A.length());
}
public String longestCommonPrefix(ArrayList<String> A) {
if (A.size() == 1) return A.get(0);
String prefix = A.get(0);
for (int i = 1; i < A.size(); i++){
prefix = findPrefixBetweenTwo(prefix, A.get(i)); // chain the earlier prefix
}
return prefix;
}