【问题标题】:Find the longest common starting substring in a set of strings [closed]在一组字符串中找到最长的公共起始子字符串[关闭]
【发布时间】:2010-12-27 08:05:20
【问题描述】:

这是一个挑战,要为一个相对琐碎的问题想出最优雅的 JavaScript、Ruby 或其他解决方案。

这个问题是Longest common substring problem 的一个更具体的情况。我只需要在数组中找到最长的公共 starting 子字符串。这大大简化了问题。

例如,[interspecies, interstelar, interstate] 中最长的子字符串是“inters”。不过,我不需要在[specifics, terrific] 中找到“ific”。

我已经通过在我的answer about shell-like tab-completion (test page here) 中快速编写一个 JavaScript 解决方案来解决这个问题。这是解决方案,稍作调整:

function common_substring(data) {
  var i, ch, memo, idx = 0
  do {
    memo = null
    for (i=0; i < data.length; i++) {
      ch = data[i].charAt(idx)
      if (!ch) break
      if (!memo) memo = ch
      else if (ch != memo) break
    }
  } while (i == data.length && idx < data.length && ++idx)

  return (data[0] || '').slice(0, idx)
}

这个code is available in this Gist 以及 Ruby 中的类似解决方案。您可以将 gist 克隆为 git repo 进行尝试:

$ git clone git://gist.github.com/257891.git substring-challenge

我对这些解决方案不太满意。我有一种感觉,它们可能会以更优雅和更少的执行复杂性来解决——这就是我发布这个挑战的原因。

我将接受我认为最优雅或最简洁的解决方案作为答案。例如,这是我想出的一个疯狂的 Ruby hack——在 String 上定义 &amp; 运算符:

# works with Ruby 1.8.7 and above
class String
  def &(other)
    difference = other.to_str.each_char.with_index.find { |ch, idx|
      self[idx].nil? or ch != self[idx].chr
    }
    difference ? self[0, difference.last] : self
  end
end

class Array
  def common_substring
    self.inject(nil) { |memo, str| memo.nil? ? str : memo & str }.to_s
  end
end

JavaScript 或 Ruby 中的解决方案是首选,但您可以用其他语言展示聪明的解决方案,只要您解释发生了什么。请只使用标准库中的代码。

更新:我最喜欢的解决方案

我选择kennebecJavaScript sorting solution 作为“答案”,因为它让我觉得既出乎意料又是天才。如果我们忽略实际排序的复杂性(假设它被语言实现无限优化),解决方案的复杂性只是比较两个字符串。

其他出色的解决方案:

感谢参与!正如您从 cmets 中看到的,我学到了很多东西(甚至是关于 Ruby)。

【问题讨论】:

  • 接受的答案展示了一个非常酷的解决方案。好吧,当然,答案本身是错误的,但谁在乎呢,这个想法太酷了;)
  • 我将这个问题作为题外话来结束,因为它是关于代码打高尔夫球和/或代码审查,而不是离散的编程问题。

标签: javascript python ruby haskell longest-prefix


【解决方案1】:

这是一个口味问题,但这是一个简单的 javascript 版本: 它对数组进行排序,然后只查看第一项和最后一项。

//数组中最长的公共起始子串

function sharedStart(array){
    var A= array.concat().sort(), 
    a1= A[0], a2= A[A.length-1], L= a1.length, i= 0;
    while(i<L && a1.charAt(i)=== a2.charAt(i)) i++;
    return a1.substring(0, i);
}

演示

sharedStart(['interspecies', 'interstelar', 'interstate'])  //=> 'inters'
sharedStart(['throne', 'throne'])                           //=> 'throne'
sharedStart(['throne', 'dungeon'])                          //=> ''
sharedStart(['cheese'])                                     //=> 'cheese'
sharedStart([])                                             //=> ''
sharedStart(['prefix', 'suffix'])                           //=> ''

【讨论】:

  • 我有另一个想法,如果你输入一个单元素数组,返回逻辑上应该是整个字符串——它确实匹配自己。原地编辑,将 tem1=A.shift() 替换为 tem1=A[0]。
  • 请注意,排序是在 O(n·log n) 中完成的。
  • 您不必对其进行排序。正确的?只需找到最小的和最大的并比较两者。
  • 如果数组只有一项或全部重复,则此方法无效。
  • @JeffMay 现在可以了。
【解决方案2】:

在 Python 中:

>>> from os.path import commonprefix
>>> commonprefix('interspecies interstelar interstate'.split())
'inters'

【讨论】:

  • 不敢相信。 Python 在 stdlib 中有这个吗?
  • 是的,但它隐藏在os.path模块中,虽然它是一个有用的字符串操作函数。
  • 干得好,蟒蛇。感觉像是在作弊。
【解决方案3】:

Ruby 单线:

l=strings.inject{|l,s| l=l.chop while l!=s[0...l.length];l}

【讨论】:

  • 这对不在结果中的每个字符遍历每个字符串一次。效率不高。
  • 它可能需要比其他解决方案更多的迭代,但我喜欢它。这就是我认为的优雅。
【解决方案4】:

我的 Haskell 单线:

import Data.List

commonPre :: [String] -> String
commonPre = map head . takeWhile (\(x:xs)-> all (==x) xs) . transpose

编辑:barkmadley 很好地解释了下面的代码。我还要补充一点,haskell 使用惰性求值,所以我们可以对 transpose 的使用感到惰性;它只会尽可能地转置我们的列表以找到公共前缀的结尾。

【讨论】:

  • 这看起来很不错的代码!你能花点时间向不懂语言的我们解释一下吗? (你不必去很深的地方)
  • 首先转置字符串。 ["abc","abd","aed"] -&gt; ["aaa","bbe","cdd"],然后你从这个列表中取出,直到有不同的东西["aaa"],然后你取出这个列表中每个列表的第一个元素(字符串只是 Haskell 中的列表),得到 "a"。从右到左更容易阅读,因为这是数据以 Haskell 的无点风格传输的方式。
  • 可爱的代码,但不幸的是,当一个字符串是另一个字符串的前缀时,这会失败,例如commonPre ["foo", "foobar", "foobarbaz"] == "foobarbaz".
【解决方案5】:

您只需要遍历所有字符串直到它们不同,然后将子字符串带到这一点。

伪代码:

loop for i upfrom 0
     while all strings[i] are equal
     finally return substring[0..i]

普通的 Lisp:

(defun longest-common-starting-substring (&rest strings)
  (loop for i from 0 below (apply #'min (mapcar #'length strings))
     while (apply #'char=
                  (mapcar (lambda (string) (aref string i))
                          strings))
     finally (return (subseq (first strings) 0 i))))

【讨论】:

  • 第一次通过找到可用选项的最小值以减少搜索树不是更理想吗?
  • @Denis:1. 没有“更优”。最优的就是最优的。在给定的 Lisp 实现中有两个可能的优化:用loops 或dos 替换apply 形式(也消除了mapcar),并在出现不匹配时立即中断第二个。 2. 这里的“可用选项的最小最大值”是什么? 3. 这里没有搜索树。如果正确结果的长度是 n,则您需要查看每个输入子字符串中索引 n 之前的每个字符。这就是这个例程所做的,它以线性方式只查看每个字符一次。
【解决方案6】:

还有另一种方法:使用正则表达式贪婪。

words = %w(interspecies interstelar interstate)
j = '='
str = ['', *words].join(j)
re = "[^#{j}]*"

str =~ /\A
    (?: #{j} ( #{re} ) #{re} )
    (?: #{j}    \1     #{re} )*
\z/x

p $1

还有一行,由 mislav 提供(50 个字符):

p ARGV.join(' ').match(/^(\w*)\w*(?: \1\w*)*$/)[1]

【讨论】:

  • 不错!单行版本是words.join(" ").match(/^(\w*)\w*(?: \1\w*)*$/)[1]。注意:当匹配包含空格的字符串时,必须稍微更新正则表达式才能使其正常工作。
【解决方案7】:

在 Python 中,除了我在另一个答案中展示的现有 commonprefix 函数之外,我不会使用任何东西,但我忍不住要重新发明轮子 :P。这是我基于迭代器的方法:

>>> a = 'interspecies interstelar interstate'.split()
>>>
>>> from itertools import takewhile, chain, izip as zip, imap as map
>>> ''.join(chain(*takewhile(lambda s: len(s) == 1, map(set, zip(*a)))))
'inters'

编辑:解释其工作原理。

zip 生成元素元组,每次取 a 的每一项:

In [6]: list(zip(*a))  # here I use list() to expand the iterator
Out[6]:
[('i', 'i', 'i'),
 ('n', 'n', 'n'),
 ('t', 't', 't'),
 ('e', 'e', 'e'),
 ('r', 'r', 'r'),
 ('s', 's', 's'),
 ('p', 't', 't'),
 ('e', 'e', 'a'),
 ('c', 'l', 't'),
 ('i', 'a', 'e')]

通过将set 映射到这些项目上,我得到了一系列独特的字母:

In [7]: list(map(set, _))  # _ means the result of the last statement above
Out[7]:
[set(['i']),
 set(['n']),
 set(['t']),
 set(['e']),
 set(['r']),
 set(['s']),
 set(['p', 't']),
 set(['a', 'e']),
 set(['c', 'l', 't']),
 set(['a', 'e', 'i'])]

takewhile(predicate, items) 在谓词为 True 时从中获取元素;在这种特殊情况下,当sets 有一个元素时,即所有单词在该位置都有相同的字母:

In [8]: list(takewhile(lambda s: len(s) == 1, _))
Out[8]:
[set(['i']),
 set(['n']), 
 set(['t']), 
 set(['e']), 
 set(['r']), 
 set(['s'])]

此时我们有一个可迭代的集合,每个集合都包含我们正在寻找的前缀的一个字母。为了构造这个字符串,我们将chain它们变成了一个单独的可迭代对象,从中我们得到join的字母到最终的字符串中。

使用迭代器的神奇之处在于所有项目都是按需生成的,因此当takewhile 停止请求项目时,压缩会在该点停止,并且不会执行任何不必要的工作。我的单行中的每个函数调用都有一个隐含的for 和一个隐含的break

【讨论】:

  • 您能否分解解决方案并详细说明使用的迭代器?
  • 为什么是“izip”和“imap”?起初以为可能是它们不区分大小写,但后来我意识到这没有多大意义
  • izipimap 返回迭代器,而 zipmap 返回列表。通过使用列表,可以生成所有元组,即使takewhile 永远不会看到其中的大部分。在 Python 3 中,zipmap 返回迭代器,因此不必从 itertools 导入它们。
  • 这让我想起了 Haskell。真正的函数式 Python 代码。
  • 学习 Haskell 确实影响了我的 Python :)
【解决方案8】:

这可能不是最简洁的解决方案(取决于您是否已经有一个库),但一种优雅的方法是使用 trie。我使用尝试在我的 Scheme 解释器中实现制表符完成:

http://github.com/jcoglan/heist/blob/master/lib/trie.rb

例如:

tree = Trie.new
%w[interspecies interstelar interstate].each { |s| tree[s] = true }
tree.longest_prefix('')
#=> "inters"

我还使用它们来匹配通道名称和 Bayeux 协议的通配符;看看这些:

http://github.com/jcoglan/faye/blob/master/client/channel.js

http://github.com/jcoglan/faye/blob/master/lib/faye/channel.rb

【讨论】:

  • 你不需要尝试。只要发生任何分支,您就完成了。
  • 真的很有趣——感谢分享!
【解决方案9】:

只是为了好玩,这里有一个用 (SWI-)PROLOG 编写的版本:

common_pre([[C|Cs]|Ss], [C|Res]) :-
  maplist(head_tail(C), [[C|Cs]|Ss], RemSs), !,
  common_pre(RemSs, Res).
common_pre(_, []).

head_tail(H, [H|T], T).

跑步:

?- S=["interspecies", "interstelar", "interstate"], common_pre(S, CP), string_to_list(CPString, CP).

给予:

CP = [105, 110, 116, 101, 114, 115],
CPString = "inters".

解释:

(SWI-)PROLOG 将字符串视为字符代码(数字)列表。所有谓词common_pre/2 所做的都是递归模式匹配,以从所有列表(所有字符串,[[C|Cs]|Ss])的列表中的第一个列表(字符串,[C|Cs])的头部选择第一个代码(C) , 并将匹配代码 C 附加到结果 iff 它对所有列表(字符串)的所有(剩余)头部都是通用的,否则它终止。

漂亮、干净、简单、高效... :)

【讨论】:

    【解决方案10】:

    基于@Svante's algorithm的javascript版本:

    function commonSubstring(words){
        var iChar, iWord,
            refWord = words[0],
            lRefWord = refWord.length,
            lWords = words.length;
        for (iChar = 0; iChar < lRefWord; iChar += 1) {
            for (iWord = 1; iWord < lWords; iWord += 1) {
                if (refWord[iChar] !== words[iWord][iChar]) {
                    return refWord.substring(0, iChar);
                }
            }
        }
        return refWord;
    }
    

    【讨论】:

      【解决方案11】:

      结合 kennebecFlorian Fjberryman 的答案,得出以下 Haskell 单行代码:

      commonPrefix l = map fst . takeWhile (uncurry (==)) $ zip (minimum l) (maximum l)
      

      使用Control.Arrow 可以获得无积分的表格:

      commonPrefix = map fst . takeWhile (uncurry (==)) . uncurry zip . (minimum &&& maximum)
      

      【讨论】:

        【解决方案12】:
        Python 2.6 (r26:66714, Oct  4 2008, 02:48:43) 
        
        >>> a = ['interspecies', 'interstelar', 'interstate']
        
        >>> print a[0][:max(
                [i for i in range(min(map(len, a))) 
                    if len(set(map(lambda e: e[i], a))) == 1]
                ) + 1]
        
        inters
        
        • i for i in range(min(map(len, a))),最大查找次数不能大于最短字符串的长度;在本例中,这将评估为 [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

        • len(set(map(lambda e: e[i], a))), 1) 为列表中的每个字符串创建一个包含i-th字符的数组; 2)制作一套; 3) 确定集合的大小

        • [i for i in range(min(map(len, a))) if len(set(map(lambda e: e[i], a))) == 1],仅包含字符集的大小为 1 的字符(该位置的所有字符都相同..);在这里它将评估为[0, 1, 2, 3, 4, 5]

        • 最后取max,加一,得到子串……

        注意:以上不适用于a = ['intersyate', 'intersxate', 'interstate', 'intersrate'],但这会:

         >>> index = len(
                 filter(lambda l: l[0] == l[1], 
                     [ x for x in enumerate(
                         [i for i in range(min(map(len, a))) 
                             if len(set(map(lambda e: e[i], a))) == 1]
                 )]))
         >>> a[0][:index]
         inters
        

        【讨论】:

        • 整洁。检查垂直集的大小很聪明
        【解决方案13】:

        如果您不太关心最终性能,这似乎并不复杂:

        def common_substring(data)
          data.inject { |m, s| s[0,(0..m.length).find { |i| m[i] != s[i] }.to_i] }
        end
        

        inject 的一个有用特性是能够使用数组的第一个元素进行预播种。这避免了零备忘检查。

        puts common_substring(%w[ interspecies interstelar interstate ]).inspect
        # => "inters"
        puts common_substring(%w[ feet feel feeble ]).inspect
        # => "fee"
        puts common_substring(%w[ fine firkin fail ]).inspect
        # => "f"
        puts common_substring(%w[ alpha bravo charlie ]).inspect
        # => ""
        puts common_substring(%w[ fork ]).inspect
        # => "fork"
        puts common_substring(%w[ fork forks ]).inspect
        # => "fork"
        

        更新:如果高尔夫是这里的游戏,那么 67 个字符:

        def f(d)d.inject{|m,s|s[0,(0..m.size).find{|i|m[i]!=s[i]}.to_i]}end
        

        【讨论】:

        • 不,这不是高尔夫。实际上,我必须查一下这个短语才能知道它的含义:) “优雅的代码”是指可读、可理解和聪明的代码。删除空格和缩短方法/变量名称与可读性和可理解性相反,并且不会使解决方案更加聪明。感谢inject 的提示!没有意识到这一点。
        • 我知道你对高尔夫的看法,但不能让乔丹的努力不受挑战。
        【解决方案14】:

        这与 Roberto Bonvallet 的解决方案非常相似,除了在 r​​uby​​ 中。

        chars = %w[interspecies interstelar interstate].map {|w| w.split('') }
        chars[0].zip(*chars[1..-1]).map { |c| c.uniq }.take_while { |c| c.size == 1 }.join
        

        第一行用一个字符数组替换每个单词。接下来,我使用zip来创建这个数据结构:

        [["i", "i", "i"], ["n", "n", "n"], ["t", "t", "t"], ...

        mapuniq 将其简化为 [["i"],["n"],["t"], ...

        take_while 将字符从数组中拉出,直到找到大小不一的字符(意味着并非所有字符都相同)。最后,我join他们回到了一起。

        【讨论】:

        • 不知道 ruby​​ 有一个 take_while——谢谢!
        【解决方案15】:

        accepted solution 已损坏(例如,对于 ['a', 'ba'] 之类的字符串,它会返回 a)。修复非常简单,您只需更改 3 个字符(从 indexOf(tem1) == -1indexOf(tem1) != 0),函数就会按预期工作。

        不幸的是,当我尝试编辑答案以修复错字时,SO 告诉我“编辑必须至少有 6 个字符”。我可以通过改进命名和可读性来改变这 3 个字符,但感觉有点太多了。

        因此,以下是肯尼贝克解决方案的固定和改进版本(至少从我的角度来看):

        function commonPrefix(words) {
          max_word = words.reduce(function(a, b) { return a > b ? a : b });
          prefix   = words.reduce(function(a, b) { return a > b ? b : a }); // min word
        
          while(max_word.indexOf(prefix) != 0) {
            prefix = prefix.slice(0, -1);
          }
        
          return prefix;
        }
        

        (在jsFiddle

        请注意,它使用reduce 方法(JavaScript 1.8)来查找字母数字最大值/最小值,而不是对数组进行排序,然后获取它的第一个和最后一个元素。

        【讨论】:

          【解决方案16】:

          在阅读这些带有所有花哨的函数式编程、排序和正则表达式等答案的答案时,我只是想:C 有什么问题?所以这是一个看起来很傻的小程序。

          #include <stdio.h>
          
          int main (int argc, char *argv[])
          {
            int i = -1, j, c;
          
            if (argc < 2)
              return 1;
          
            while (c = argv[1][++i])
              for (j = 2; j < argc; j++)
                if (argv[j][i] != c)
                  goto out;
          
           out:
            printf("Longest common prefix: %.*s\n", i, argv[1]);
          }
          

          编译它,用你的字符串列表作为命令行参数运行它,然后支持我使用goto

          【讨论】:

            【解决方案17】:

            这是在 Ruby 中使用正则表达式的解决方案:

            def build_regex(string)
              arr = []
              arr << string.dup while string.chop!
              Regexp.new("^(#{arr.join("|")})")
            end
            
            def substring(first, *strings)
              strings.inject(first) do |accum, string|
                build_regex(accum).match(string)[0]
              end
            end
            

            【讨论】:

            • 我对它的表现很感兴趣。我无法决定它是否会比参考解决方案更快或更慢。但我知道你已经在编写基准测试了 ;) 耐心等待
            • 你不需要正则表达式,因为你只需要线性字符串比较。这种实现效率特别低,因为您在每次迭代中都构建了一个新的正则表达式。
            【解决方案18】:

            我会做以下事情:

            1. 将数组的第一个字符串作为初始起始子字符串
            2. 获取数组的下一个字符串并比较字符,直到到达其中一个字符串的末尾或发现不匹配。如果发现不匹配,将起始子字符串减少到发现不匹配的长度。
            3. 重复第 2 步,直到测试完所有字符串。

            这是一个 JavaScript 实现:

            var array = ["interspecies", "interstelar", "interstate"],
                prefix = array[0],
                len = prefix.length;
            for (i=1; i<array.length; i++) {
                for (j=0, len=Math.min(len,array[j].length); j<len; j++) {
                    if (prefix[j] != array[i][j]) {
                        len = j;
                        prefix = prefix.substr(0, len);
                        break;
                    }
                }
            }
            

            【讨论】:

            • 这是低效的,因为当后面的字符串进一步缩短结果时,您可能会进行多余的比较。
            【解决方案19】:

            您可以只获取字符串的最小值和最大值,而不是排序。

            对我来说,计算机程序的优雅是速度和简单性的平衡。 它不应该进行不必要的计算,并且应该足够简单以证明其正确性。

            我可以称排序解决方案“聪明”,但不能称其为“优雅”。

            【讨论】:

              【解决方案20】:

              通常使用成熟的开源库比使用自己的开源库更优雅。然后,如果它不能完全满足您的需求,您可以对其进行扩展或修改以改进它,并让社区决定它是否属于该库。

              diff-lcs 是一个很好的 Ruby gem,用于最不常见的子字符串。

              【讨论】:

                【解决方案21】:

                我的 Java 解决方案:

                public static String compute(Collection<String> strings) {
                    if(strings.isEmpty()) return "";
                    Set<Character> v = new HashSet<Character>();
                    int i = 0;
                    try {
                        while(true) {
                            for(String s : strings) v.add(s.charAt(i));
                            if(v.size() > 1) break;
                            v.clear();
                            i++;
                        }
                    } catch(StringIndexOutOfBoundsException ex) {}
                    return strings.iterator().next().substring(0, i);
                }
                

                【讨论】:

                • 不是 Unicode 安全的。 :p
                【解决方案22】:

                Golfed JS 解决方案只是为了好玩:

                w=["hello", "hell", "helen"];
                c=w.reduce(function(p,c){
                    for(r="",i=0;p[i]==c[i];r+=p[i],i++){}
                    return r;
                });
                

                【讨论】:

                  【解决方案23】:

                  这是一个有效的 ruby​​ 解决方案。我基于高/低猜谜游戏的策略理念,在最长前缀上迭代归零。

                  如果我错了有人纠正我,但我认为复杂度是 O(n log n),其中 n 是最短字符串的长度,字符串的数量被认为是一个常数。

                  def common(strings)
                    lo = 0
                    hi = strings.map(&:length).min - 1
                    return '' if hi < lo
                  
                    guess, last_guess = lo, hi
                  
                    while guess != last_guess
                      last_guess = guess
                      guess = lo + ((hi - lo) / 2.0).ceil
                  
                      if strings.map { |s| s[0..guess] }.uniq.length == 1
                        lo = guess
                      else
                        hi = guess
                      end
                    end
                  
                    strings.map { |s| s[0...guess] }.uniq.length == 1 ? strings.first[0...guess] : ''
                  end
                  

                  还有一些检查它是否有效:

                  >> common %w{ interspecies interstelar interstate }
                  => "inters"
                  
                  >> common %w{ dog dalmation }
                  => "d"
                  
                  >> common %w{ asdf qwerty }
                  => ""
                  
                  >> common ['', 'asdf']
                  => ""
                  

                  【讨论】:

                    【解决方案24】:

                    有趣的替代 Ruby 解决方案:

                    def common_prefix(*strings)
                      chars  = strings.map(&:chars)
                      length = chars.first.zip( *chars[1..-1] ).index{ |a| a.uniq.length>1 }
                      strings.first[0,length]
                    end
                    
                    p common_prefix( 'foon', 'foost', 'forlorn' ) #=> "fo"
                    p common_prefix( 'foost', 'foobar', 'foon'  ) #=> "foo"
                    p common_prefix( 'a','b'  )                   #=> ""
                    

                    如果您使用chars = strings.sort_by(&amp;:length).map(&amp;:chars),它可能有助于加快速度,因为第一个字符串越短,zip 创建的数组就越短。但是,如果您关心速度,您可能无论如何都不应该使用此解决方案。 :)

                    【讨论】:

                      【解决方案25】:

                      AShelly 的优秀答案的 Javascript 克隆。

                      需要Array#reduce,仅在 Firefox 中受支持。

                      var strings = ["interspecies", "intermediate", "interrogation"]
                      var sub = strings.reduce(function(l,r) { 
                          while(l!=r.slice(0,l.length)) {  
                              l = l.slice(0, -1);
                          }
                          return l;
                      });
                      

                      【讨论】:

                      • 这对不在结果中的每个字符遍历每个字符串一次;例如,结果加上 10 个字符的字符串被遍历 10 次。这不是很有效。
                      【解决方案26】:

                      这绝不是优雅的,但如果你想要简洁:

                      Ruby,71 个字符

                      def f(a)b=a[0];b[0,(0..b.size).find{|n|a.any?{|i|i[0,n]!=b[0,n]}}-1]end
                      

                      如果你想要展开它,它看起来像这样:

                      def f(words)
                        first_word = words[0];
                        first_word[0, (0..(first_word.size)).find { |num_chars|
                          words.any? { |word| word[0, num_chars] != first_word[0, num_chars] }
                        } - 1]
                      end
                      

                      【讨论】:

                      • 显然这不适用于单元素数组,但在其他方面非常紧凑。
                      • 添加return first_word if words.length==1作为函数的第二行,它也应该处理单元素数组。
                      【解决方案27】:

                      这不是代码高尔夫,但您要求有点优雅,我倾向于认为递归很有趣。 Java。

                      /** Recursively find the common prefix. */
                      public String findCommonPrefix(String[] strings) {
                      
                          int minLength = findMinLength(strings);
                      
                          if (isFirstCharacterSame(strings)) {
                              return strings[0].charAt(0) + findCommonPrefix(removeFirstCharacter(strings));
                          } else {
                              return "";
                          }
                      }
                      
                      /** Get the minimum length of a string in strings[]. */
                      private int findMinLength(final String[] strings) {
                          int length = strings[0].size();
                          for (String string : strings) {
                              if (string.size() < length) {
                                  length = string.size();
                              }
                          }
                          return length;
                      }
                      
                      /** Compare the first character of all strings. */
                      private boolean isFirstCharacterSame(String[] strings) {
                          char c = string[0].charAt(0);
                          for (String string : strings) {
                              if (c != string.charAt(0)) return false;
                          }
                      
                          return true;
                      }
                      
                      /** Remove the first character of each string in the array, 
                          and return a new array with the results. */
                      private String[] removeFirstCharacter(String[] source) {
                          String[] result = new String[source.length];
                          for (int i=0; i<result.length; i++) {
                              result[i] = source[i].substring(1); 
                          }
                          return result;
                      }
                      

                      【讨论】:

                      • 我不会谴责递归,但您正在构建一个新字符串来连接结果的每个字符。否则,该方法是可以的。
                      【解决方案28】:

                      基于@Svante 算法的红宝石版本。运行速度大约是我第一个的 3 倍。

                       def common_prefix set 
                         i=0
                         rest=set[1..-1]
                         set[0].each_byte{|c|
                           rest.each{|e|return set[0][0...i] if e[i]!=c}
                           i+=1
                         }
                         set
                       end
                      

                      【讨论】:

                        【解决方案29】:

                        我的 Javascript 解决方案:

                        IMOP,使用排序太棘手了。 我的解决方案是通过循环数组逐字比较。 如果字母没有被匹配,则返回字符串。

                        这是我的解决方案:

                        var longestCommonPrefix = function(strs){
                            if(strs.length < 1){
                                return '';
                            }
                        
                            var p = 0, i = 0, c = strs[0][0];
                        
                            while(p < strs[i].length && strs[i][p] === c){
                                i++;
                                if(i === strs.length){
                                    i = 0;
                                    p++;
                                    c = strs[0][p];
                                }
                            }
                        
                            return strs[0].substr(0, p);
                        };
                        

                        【讨论】:

                          【解决方案30】:

                          意识到这有可能变成 code golf 的匹配项(或者这是意图?),这是我使用 sed 的解决方案,从 my answer 复制到另一个 SO 问题并缩短为 36 个字符(30 个这是实际的sed 表达式)。它期望在标准输入或作为附加参数传递的文件中提供字符串(每个在单独的行上)。

                          sed 'N;s/^\(.*\).*\n\1.*$/\1\n\1/;D'
                          

                          shebang 行中带有 sed 的脚本的大小为 45 个字符:

                          #!/bin/sed -f
                          N;s/^\(.*\).*\n\1.*$/\1\n\1/;D
                          

                          脚本的测试运行(名为longestprefix),字符串作为“此处文档”提供:

                          $ ./longestprefix <<EOF
                          > interspecies
                          > interstelar
                          > interstate
                          > EOF
                          inters
                          $
                          

                          【讨论】:

                            猜你喜欢
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2016-03-16
                            • 2020-07-31
                            • 1970-01-01
                            • 1970-01-01
                            • 1970-01-01
                            • 2013-09-27
                            相关资源
                            最近更新 更多