【问题标题】:Algorithm for largest word formed from perodic table elements由周期表元素组成的最大单词的算法
【发布时间】:2014-05-15 09:07:42
【问题描述】:

我想为下面的问题场景写一个算法

取元素周期表元素的名字,找出能组成的最大单词? NaNe等符号应视为单个元素。

这是在一家知名公司的求职面试中提出的。 谁能帮我解决这个问题。

【问题讨论】:

  • 是否提供了词典来检查输出?
  • I.N.C.O.N.S.P.I.C.U.O.U.S.N.Es.S
  • 如果你想找到最长的单词,你的第一步就是将你的字典从最长到最短排序。然后从顶部开始尝试,找到第一个可以用元素周期表中的符号构建的。
  • @Shahbaz 仅按长度对字典进行排序并在第一个匹配处停止并不一定会给出正确的答案,因为多字母元素仅计为一个符号。也许这个问题的目的是看看你是否想变得聪明并优化一个错误。
  • 找到可以组成的最大单词实际上是模棱两可的。他们需要最大的元素组合,还是最大的结果词?

标签: algorithm word


【解决方案1】:

我认为更好的方法是检查字典中的每个单词,看看它是否可以由元素的名称组成。检查元素的每个排列将更难编程并且效率更低。

虽然我同意生成组合更容易,但它们的数量太多了,而且正如你所说,如果你不给出限制,它们会趋于无穷大。带有符号的单词的产生会稍微困难和挑剔,但我认为不会太难。

例如当你得到一个词时,你可以搜索元素寻找可以组成你的词的元素,然后使用那组元素尝试从头到尾填写字母。显然,对于不是 2 个字母和长度奇数的单词的元素,这会变得更加困难。

您实际上可以使用一种图表。假设你有“硅”。 您可以从表中的字母“S”或“SI”开始。从那里选择“SI”,因为它更接近您的解决方案。如果“SI”不能导致您的解决方案,您将不得不回来看看“S”是否有效。

所以它是一种深度优先搜索。

【讨论】:

  • 你建议如何检查一个词是否可以由符号组成?这对问题和答案非常重要。此外,编程生成组合 IMO 非常容易。
  • 嗯……再想一想,你可能是对的,包括符号的重复会使可能的单词无限。发明算法来检查单词是否可以构建可能会更容易。
  • 我想出了一个算法来检查单词的前缀是否是一个符号。但同样,你当然是对的。对于每个可能的命中,您应该制作一个列表(一个队列),并尝试所有可能的路径。我认为用这个想法来重构和扩展我的建议仍然是相当容易的。我认为基本的想法很好。
  • 我认为“一种 DFS”更好的措辞是构建一个与该词匹配的前缀图。而且 IMO 像 DFS 或 BFS 一样工作并不重要。
  • 您可以使用 DP 来检查一个单词是否仅由元素名称组成:令 f(i) 为可以形成前缀 w[1..i] 的最大元素数。那么如果有一个大小为 k 的元素字符串 E 并且我们有 w[i+1..i+k] = E,我们有 f(i + k)
【解决方案2】:

如何将给定的单词表示为化合物?这是一个动态编程解决方案。重要的一行是“让progress[i] 成为word[:i] 子问题的解决方案”。其余的紧随其后。

elements = "H He Li Be B C N O F Ne Na Mg Al Si P S Cl Ar K Ca Sc Ti V Cr Mn Fe Co Ni Cu Zn Ga Ge As Se Br Kr Rb Sr Y Zr Nb Mo Tc Ru Rh Pd Ag Cd In Sn Sb Te I Xe Cs Ba La Ce Pr Nd Pm Sm Eu Gd Tb Dy Ho Er Tm Yb Lu Hf Ta W Re Os Ir Pt Au Hg Tl Pb Bi Po At Rn Fr Ra Ac Th Pa U Np Pu Am Cm Bk Cf Es Fm Md No Lr Rf Db Sg Bh Hs Mt Ds Rg Cn Uut Fl Uup Lv Uus Uuo".split()

def decompose(word):
    """Express given word as chemical compound. If there are multiple solutions, return one of minimal weight."""
    progress = [False for x in range(len(word)+1)] # solution for word[:i]
    progress[0] = []

    for i in range(1, len(word)+1):
        possibles = list()
        for j in range(max(i-3,0), i):
            if progress[j] == False:
                continue
            alchemical = word[j:i].title()
            if alchemical in elements:
                possibles.append(progress[j] + [alchemical])

        if possibles:
            # choose minimal solution
            progress[i] = min(possibles, key=len)

    if progress[-1] == False:
        return False
    return "".join(progress[-1])

assert decompose("sine") == "SiNe" # rather than S-I-Ne
assert decompose("bismuth") == "BiSmUTh"
assert decompose("jam") == False

https://gist.github.com/hickford/750135db633832913703


在整本字典上运行这个,最长的复合词是:

  • 非具象主义NoNRePReSeNTaTiONaLiSm
  • 热磷光ThErMoPHOsPHoReSCeNCe
  • 支气管食管镜检查BrONCHoEsOPHAgOsCoPY
  • 高磷光HYPErPHOsPHoReSCeNCe
  • 短头颅畸形HYPSiBrAcHYCePHAlISm

【讨论】:

    【解决方案3】:

    生成所有单词并检查它们是否存在是不切实际的。有 118^L 个长度为 L 的单词,这是一个增长过快的函数。 1,643,032 三个符号字!

    反过来,正如 Makoto 所建议的那样,效率更高。尝试从字典中重建每个单词。大约有 250,000 个英文单词。

    检查单词很简单:查看是否有任何元素符号与单词的开头匹配,然后继续检查剩余的字符。

    您必须尝试所有可能的匹配项,因为匹配项可能会隐藏另一个匹配项。 (我的意思是单词 ABC 可以被符号 A 匹配,然后因为 BC 不可用而被卡住,但可能是 AB 和 C 是。)所以匹配函数将是递归的。我预计这个匹配过程不会呈指数级增长。

    为了获得最大效率,您会将符号存储在 trie 结构 http://en.wikipedia.org/wiki/Trie

    最后提示:由于您只是被要求找到最长的匹配项,因此请通过减少长度来尝试单词并在第一个匹配项处停止。

    这是一个简单的 Python 解决方案,没有任何优化。匹配是从右到左完成的,以允许按后序打印符号序列:

    Words= ['K', 'NaH', 'NaNaNaNa', 'HaKuNa']
    Symbols= ['Na','K','H']
    
    def Match(Word):
        # Match the end of the word with every symbol
        for S in Symbols:
            # In case of a partial match, recurse on the prefix
            if S == Word or (S == Word[-len(S):] and Match(Word[:-len(S)])):
                print S,
                return True
    
        # No match, report failure
        return False
    
    for W in Words:
        if Match(W):
            print
    
    
    >>> 
    K
    Na H
    Na Na Na Na
    

    【讨论】:

    • 对字典进行排序并在第一个匹配时停止可能会给出错误的答案,因为多字母元素仅计为一个符号。
    • @Daniel:对。这可以通过继续搜索直到单词长度等于找到的最长单词的符号数来解决。
    • 您可以使用 DP 进行多项式时间检查
    • @Niklas: DP 在这里如何提供帮助?
    • 如果你只是记住Match,你已经很成功了,因为它只使用 O(n*m) 不同的输入调用,其中 n 是单词数,m 是最大单词大小
    【解决方案4】:

    生成所有可能的词 - 幼稚的方法。

    基于Generate all permutations of all lengths:

    import itertools..
    symbols = ['Na','K','H']
    for i in range(len(symbols)):
     for word in itertools.permutations(symbols,i+1):
      print( ''.join(word) )
    

    您可以生成所有可能的组合,并根据字典检查它是否是实际单词。但它效率低下,并且仅适用于不允许重复符号的情况。

    检查是否可以从符号构建单词 - 仍然不完美。

    如果您允许重复,您需要根据符号列表检查一个单词。我提出以下建议:

    import itertools..
    words = ['K', 'NaH', 'NaNaNaNa', 'HaKuNa']
    symbols = ['Na','K','H']
    for i in range(len(symbols)):
     for word in itertools.permutations(symbols,i+1):
      print( ''.join(word) )
    
    
    def can_be_built(word):
      pos = 0
      ret = True
      while(pos < len(word)):
       #following loop checks if word starting form `pos`
       #can be prefixed by a symbol
       symbol_match = False
       for symbol in symbols:
        if word[pos:pos+len(symbol)] == symbol:
          symbol_match = True
          break
       if symbol_match == False:
         print('Word `{}` has no match for symbol from: {}'.format(word, word[pos:]))
         ret = False
         break
       #if it does move forward
       pos += len(symbol)
    
      return ret
    
    for word in words:
       print("{} can be built? {}".format(word, can_be_built(word)))
    

    它迭代地检查一个单词前缀是否是一个符号,然后向前移动直到到达单词的结尾。

    输出:

    K can be built? True
    NaH can be built? True
    NaNaNaNa can be built? True
    Word `HaKuNa` has no match for symbol from: aKuNa
    HaKuNa can be built? False
    

    它仍然不完美。

    正确的方法

    正如 Makoto 所说,前缀检查应该返回每个可能匹配项的列表。该算法应该从这些匹配中创建一个队列,并检查所有可能的路径。这有点像构建一个匹配单词的前缀图。如果一个人构建了整个单词,你就回家了。

    我认为更正我的第二个示例仍然相当容易,但我没有时间编写实际代码。我认为这是一个很好的起点。

    【讨论】:

    • 您能详细说明您的解决方案吗?
    • 因此,您选择不将周期表中的所有符号添加到字符串数组中。您的算法似乎非常适合特定语言。
    • 我添加了第二个例子,允许重复,以防蝙蝠侠想说NaNaNa。例子是用python写的。
    • 我觉得上面的代码有点意思。我会尝试一下。谢谢!
    • 我编辑了带有注释的代码,以帮助识别前缀检查。但我认为重构它会更好,你当然需要让它捕捉所有可能的匹配,而不仅仅是像现在这样的第一个。
    【解决方案5】:

    [编辑:这篇文章基本上只是对 Niklas B. 在其他帖子的 cmets 中提到的 DP 算法的完整解释。]

    在线性时间内测试特定单词

    在一个可以由化学元素名称组成的单词中,以下至少一项必须为真:

    • 最后一个字母是一个单字母的化学元素名称,并且由除最后一个之外的所有字母组成的前缀本身就是一个可以由化学元素名称组成的单词。
    • 最后两个字母是一个由两个字母组成的化学元素名称,并且由除最后两个之外的所有字母组成的前缀本身就是一个可以由化学元素名称组成的单词。

    这提出了一个不错的 DP 算法,其中对于长度为 k 的给定单词 w,我们计算对于所有 1

    让 X = 1 最大化元素名称,或 2 最大化字母数。

    DP 的递归可以写成:

    • one(i) = if (w[i] 是 1 个字母的元素名称) { f(i-1) + 1 } else { -1 }
    • two(i) = if ("w[i-1]w[i]" 是 2 个字母的元素名称) { f(i-2) + X } else { -1 }
    • f(i) = -1 如果 i
    • f(0) = 0
    • f(i) = max(one(i), two(i)) if i > 0

    那么 f(k) 将是我们想知道的:可以形成 w 的(字母/元素)的最大数量,如果不可能,则为 -1。

    max() 表示如果只有一种处理前缀结尾的方式有效,则将选择该方式(因为另一种方式的得分为 -1,总是比较少),并且如果两种方法都行不通,我们将正确地得到 f(i) = -1。

    假设单个字符或字符对是否是有效的化学元素名称的恒定时间测试(使用例如数组或哈希表查找),我们可以计算给定单词是否可以表示为时间和空间上的化学元素名称序列与其长度成正比。

    考虑所有单词

    如果我们要最大化字母的数量,那么按长度递减顺序处理字典可能最有意义,因为我们第一次遇到 f(k) 不为 -1 的单词时,我们就知道了必须是最长的,我们可以停下来。

    这可以调整为最大化元素名称计数。虽然我们不能立即停止,因为可能还有一个更短的词,但是可以由更多的元素名称组成(具体来说,通过使用更多的单字符名称),我们仍然可以在找到时得到一些有用的信息一个新词的元素数比以前最好的要多:我们可以用这个元素数更新一个截止阈值,一旦我们看到一个字母少于这个阈值的词就停止,因为长度为 m 的词永远不可能由 更多 个元素名称组成。

    有不同的方法,结果可能更快:通过首先按字母顺序对字典进行排序,我们可以避免在与前一个单词共享的前缀上重新计算 f(i)。例如。如果carton 紧跟在字典中的cart 之后,那么我们只需要在on 部分计算f(i)。

    【讨论】:

      【解决方案6】:

      只是想写下我的想法,并与算法专业的其他人一起验证,因为我在其他地方没有看到这样的问题。

      顺便说一句,我使用 Java。这是我的伪代码:

      1、使用元素周期表的元素符号组成字符串数组:String[] PeriTable。

      2、基于回溯思想,构建一个名为Backtracking(String result, String[] PeriTable, String[] used)的辅助函数;

      3、我们迭代PeriTable中的每个元素,并检查它形成的String。

       Backtracking void(String result, String[] PeriTable, String[] used){
         for(int i=0;i<PeriTable.length;i++){
           If(!used.contain(PeriTable[i])){
            newResult=result+PeriTable[i];
           If(isEnglishWord(newResult)) {
               used.add(PeriTable[i]);
               maxlength=Math.max(maxlength,newResult.length());
               Backtracking(newResult, PeriTable, used);
               used.remove(used.length()-1);
            }
          }
          else continue;
        }
       }
      

      但是,我不知道如何构建 isEnglishWord();功能。

      【讨论】:

        【解决方案7】:

        对不起,我对这些答案很困惑。当然,显而易见的答案是通过使用嵌套桶排序到某个深度(例如 8 个字母)按字母顺序对字典进行排序,这应该可以让您以 1 个顺序检索以给定字母序列开始的单词,最多 8 个。

        然后通过周期表进行 DFS,就像它是一个游戏树一样。在每一步都添加一个元素,然后检查列表以查看是否有任何以该组合开头的单词。

        这将相对占用大量内存,因为您可能需要至少 6 层存储桶(按前六个字母排序),但通常只有 1,000,000 个左右的单词。由于实际上游戏树中的每个分支都会在四个字母后终止,因此您的 DFS 将非常快速地搜索整个游戏树。如果你能组成字典中的每一个词,那么它仍然必须是最多有与单词相同数量的分支,而实际上你只能得到字典中单词的一小部分,所以这种方法一定很有效。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2016-01-16
          • 1970-01-01
          • 1970-01-01
          • 2021-08-12
          • 1970-01-01
          • 1970-01-01
          • 2023-04-11
          • 1970-01-01
          相关资源
          最近更新 更多