【问题标题】:Algorithm Issue: letter combinations算法问题:字母组合
【发布时间】:2008-09-19 14:51:37
【问题描述】:

我正在尝试编写一段代码来执行以下操作:

取数字 0 到 9 并为该数字分配一个或多个字母。例如:

0 = N,
1 = L,
2 = T,
3 = D,
4 = R,
5 = V or F,
6 = B or P,
7 = Z,
8 = H or CH or J,
9 = G

当我有一个像 0123 这样的代码时,对它进行编码就很容易了。它显然会构成代码 NLTD。当引入像 5,6 或 8 这样的数字时,情况会有所不同。像 051 这样的数字会导致不止一种可能性:

NVL 和 NFL

很明显,如果数字较长,包括 5,6 或 8 等多个数字,情况会变得更加“糟糕”。

由于数学很差,我还没有想出一个体面的解决方案,让我可以给程序输入一堆数字并让它吐出所有可能的字母组合。所以我很想得到一些帮助,因为我似乎无法弄清楚。挖掘了一些关于排列组合的信息,但没有运气。

感谢您提供任何建议/线索。我需要编写代码的语言是 PHP,但任何一般性提示都将受到高度赞赏。

更新:

更多背景知识:(非常感谢您的快速回复!)

我的问题背后的想法是构建一个脚本,该脚本将帮助人们轻松地将他们想要记住的数字转换为更容易记住的单词。这有时被称为“伪命理”。

我希望脚本为我提供所有可能的组合,然后将这些组合保存在剥离单词的数据库中。这些被删除的单词只是来自字典,并且我在问题中提到的所有字母都被删除了。这样,要编码的数字通常可以很容易地与一个或多个数据库记录相关联。当这种情况发生时,您最终会得到一个单词列表,您可以使用这些单词来记住您想记住的数字。

【问题讨论】:

  • 这听起来确实是一个不错的面试问题。可以要求迭代和递归解决方案,看看这个人想出了什么。我必须记住这一点。 :)
  • 这不公平,我的大脑在转圈时总是工作得更好更快。尤其是,天哪,又不是1:30了吧?是的,我该走了……
  • 为什么似乎每个人都会自动跳到递归来解决这个问题?我是唯一一个认为递归对于这个问题来说是矫枉过正的人吗?
  • 为什么递归是多余的?使用最好的工作工具,如果你可以制作一个完成工作的五行递归函数,有什么问题?我选择了它,因为通过递归而不是通过交互来查看问题时,我的大脑工作得更好。
  • 对我来说,所有那些可以由 3 个循环处理的东西的堆栈帧似乎都过分了。但你是对的;对程序员最有意义的东西。我倾向于避免递归,除非无论如何我都需要一个堆栈进行回溯。迭代方法似乎不太容易出错并且更容易理解。

标签: algorithm unique combinations


【解决方案1】:

它可以很容易地递归完成。

这个想法是,要处理大小为 n 的整个代码,您必须首先处理 n - 1 位数字。 一旦您获得了 n-1 位数字的所有答案,就可以通过在最后一个数字后面附加正确的字符来推断整个答案。

【讨论】:

    【解决方案2】:

    实际上有一个比枚举数字的所有可能翻译并查找它们更好的解决方案:只需对字典中的每个单词进行反向计算,然后将数字字符串存储在另一个字段中。因此,如果您的映射是:

    0 = N,
    1 = L,
    2 = T,
    3 = D,
    4 = R,
    5 = V or F,
    6 = B or P,
    7 = Z,
    8 = H or CH or J,
    9 = G
    

    你的反向映射是:

    N = 0,
    L = 1,
    T = 2,
    D = 3,
    R = 4,
    V = 5,
    F = 5,
    B = 6,
    P = 6,
    Z = 7,
    H = 8,
    J = 8,
    G = 9
    

    请注意,'ch' 没有映射,因为 'c' 将被删除,而 'h' 无论如何都会转换为 8。

    然后,您所要做的就是遍历字典单词中的每个字母,如果匹配则输出相应的数字,如果不匹配则不执行任何操作。

    将所有生成的数字字符串作为另一个字段存储在数据库中。当您想要查找某些内容时,只需对输入的数字执行一个简单的查询,而不必对潜在单词进行数十(或数百或数千)次查找。

    【讨论】:

      【解决方案3】:

      您要保存数字的一般结构 -> 字母分配是一个或多个数组,类似于:

      // 0 = N, 1 = L, 2 = T, 3 = D, 4 = R, 5 = V or F, 6 = B or P, 7 = Z, 
      // 8 = H or CH or J, 9 = G
      $numberMap = new Array (
          0 => new Array("N"),
          1 => new Array("L"),
          2 => new Array("T"),
          3 => new Array("D"),
          4 => new Array("R"),
          5 => new Array("V", "F"),
          6 => new Array("B", "P"),
          7 => new Array("Z"),
          8 => new Array("H", "CH", "J"),
          9 => new Array("G"),
      );
      

      然后,一点递归逻辑给我们一个类似的函数:

      function GetEncoding($number) {
          $ret = new Array();
          for ($i = 0; $i < strlen($number); $i++) {
              // We're just translating here, nothing special.
              // $var + 0 is a cheap way of forcing a variable to be numeric
              $ret[] = $numberMap[$number[$i]+0];
          }
      }
      
      function PrintEncoding($enc, $string = "") {
          // If we're at the end of the line, then print!
          if (count($enc) === 0) {
              print $string."\n";
              return;
          }
      
          // Otherwise, soldier on through the possible values.
          // Grab the next 'letter' and cycle through the possibilities for it.
          foreach ($enc[0] as $letter) {
              // And call this function again with it!
              PrintEncoding(array_slice($enc, 1), $string.$letter);
          }
      }
      

      为递归喝彩三声!这将通过以下方式使用:

      PrintEncoding(GetEncoding("052384"));
      

      如果你真的想要它作为一个数组,使用输出缓冲并使用“\n”作为你的拆分字符串来爆炸。

      【讨论】:

        【解决方案4】:

        这类问题通常用递归来解决。在红宝石中,一个(快速而肮脏的)解决方案是

        @values = Hash.new([])
        
        
        @values["0"] = ["N"] 
        @values["1"] = ["L"] 
        @values["2"] = ["T"] 
        @values["3"] = ["D"] 
        @values["4"] = ["R"] 
        @values["5"] = ["V","F"] 
        @values["6"] = ["B","P"] 
        @values["7"] = ["Z"] 
        @values["8"] = ["H","CH","J"] 
        @values["9"] = ["G"]
        
        def find_valid_combinations(buffer,number)
         first_char = number.shift
         @values[first_char].each do |key|
          if(number.length == 0) then
             puts buffer + key
          else
             find_valid_combinations(buffer + key,number.dup)
            end
         end
        end
        
        find_valid_combinations("",ARGV[0].split(""))
        

        如果你从命令行运行它,你会得到:

        $ ruby r.rb 051
        NVL
        NFL
        

        这与brute-force searchbacktracking有关

        【讨论】:

          【解决方案5】:

          这是 Python 中的递归解决方案。

          #!/usr/bin/env/python
          
          import sys
          
          ENCODING = {'0':['N'],
                      '1':['L'],
                      '2':['T'],
                      '3':['D'],
                      '4':['R'],
                      '5':['V', 'F'],
                      '6':['B', 'P'],
                      '7':['Z'],
                      '8':['H', 'CH', 'J'],
                      '9':['G']
                      }
          
          def decode(str):
             if len(str) == 0:
                 return ''
             elif len(str) == 1:
                 return ENCODING[str]
             else:
                 result = []
                 for prefix in ENCODING[str[0]]:
                     result.extend([prefix + suffix for suffix in decode(str[1:])])
                 return result
          
          if __name__ == '__main__':
             print decode(sys.argv[1])
          

          示例输出:

          $ ./demo 1
          ['L']
          $ ./demo 051
          ['NVL', 'NFL']
          $ ./demo 0518
          ['NVLH', 'NVLCH', 'NVLJ', 'NFLH', 'NFLCH', 'NFLJ']
          

          【讨论】:

            【解决方案6】:

            您能做到以下几点: 创建一个结果数组。 在数组中创建一个值为“”的项目

            遍历数字,比如 051 分别分析每个数字。

            每次找到一个数字之间的 1 对 1 匹配时,将正确的值添加到结果数组中的所有项目。 所以 "" 变成了 N。

            每次找到一对多匹配时,使用一个选项将新行添加到结果数组,并使用另一个选项更新现有结果。 所以 N 变成了 NV 并创建了一个新项目 NF

            那么最后一个数字是 1 对 1 匹配,因此结果数组中的项目变为 NVL 和 NFL

            通过结果数组循环生成结果,打印它们,或者其他什么。

            【讨论】:

              【解决方案7】:

              p<sub>n</sub> 是给定数字字符串s 直到n<sup>th</sup> 数字的所有可能字母组合的列表。

              那么,下面的算法会生成p<sub>n+1</sub>

              digit = s[n+1];
              foreach(letter l that digit maps to)
              {
                  foreach(entry e in p(n))
                  {
                      newEntry = append l to e;
                      add newEntry to p(n+1);
                  }
              }
              

              第一次迭代有点特殊,因为 p-1 是未定义的。您可以简单地将 p0 初始化为第一个字符的所有可能字符的列表。

              所以,你的 051 示例:

              迭代 0:

              p(0) = {N}
              

              迭代 1:

              digit = 5
              foreach({V, F})
              {
                  foreach(p(0) = {N})
                  {
                      newEntry = N + V  or  N + F
                      p(1) = {NV, NF}
                  }
              }
              

              迭代 2:

              digit = 1
              foreach({L})
              {
                  foreach(p(1) = {NV, NF})
                  {
                      newEntry = NV + L  or  NF + L
                      p(2) = {NVL, NFL}
                  }
              }
              

              【讨论】:

              • 出于好奇,有人知道这是什么语言吗?或者它只是某种形式的伪代码?我不认识 foreach 语法,尤其是在迭代中。
              【解决方案8】:

              你想要的形式大概是这样的:

              function combinations( $str ){
              $l = len( $str );
              $results = array( );
              if ($l == 0) { return $results; }
              if ($l == 1)
              {  
                 foreach( $codes[ $str[0] ] as $code )
                 {
                  $results[] = $code;
                 }
                 return $results;
              }
              $cur = $str[0];
              $combs = combinations( substr( $str, 1, $l ) );
              foreach ($codes[ $cur ] as $code)
              {
                  foreach ($combs as $comb)
                  {
                      $results[] = $code.$comb;
                  }
              }
              return $results;}
              

              这很难看,pidgin-php 所以请先验证一下。基本思想是从 [1..n] 生成字符串的每个组合,然后在所有这些组合的前面添加 str[0] 的每个可能代码。请记住,在最坏的情况下,这将使您的字符串长度呈指数级增长,因为您的编码方案中实际上存在很多歧义。

              【讨论】:

              • 有晚上和早上,有迭代和递归的解决方案,一切都很好。
              【解决方案9】:

              诀窍不仅在于生成与给定数字匹配的所有可能的字母组合,还在于选择最容易记住的字母序列。建议对每个序列运行 soundex 算法,并尝试与英语词典(例如 Wordnet)进行匹配,以找到最“真实单词”的序列。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2022-01-12
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2012-06-10
                • 1970-01-01
                • 2011-02-15
                • 1970-01-01
                相关资源
                最近更新 更多