使用 Ruby 查找给定编码字符串的组合

Posted

技术标签:

【中文标题】使用 Ruby 查找给定编码字符串的组合【英文标题】:Find the combinations of a given encoded string using Ruby 【发布时间】:2013-12-24 08:12:49 【问题描述】:

我在一次采访中被问到这个问题,但我无法找到令人满意的解决方案。如果有人能指点一下,将不胜感激。

给定一个像

这样的映射
  mapping = "A" => 1, "B" => 2, "C" => 3..... "Z" => 26

 encode("A") == "1"
 encode("BA") == "21"
 encode("ABC") == "123"
 encode("") == ""


decode("1") == ["A"] -> 1
decode("21") == ["BA", "V"] -> 2
decode("123") == ["ABC", "JC", "AX"] -> 3
decode("012") == [] -> 0
decode("") == [""] -> 1
decode("102") == ["JB"] -> 1


numDecode(X) == len(decode(X))
numDecode("1") == 1
numDecode("21") == 2
numDecode("123") == 3
numDecode("") == 1
numDecode("102") == 1
numDecode("012") == 0

我们需要一个 numDecode 方法来给出唯一解数组的长度。

更新:

给定如下映射:

mapping = "A" => 1, "B" => 2, "C" => 3..... "Z" => 26

Suppose we are given a string as "A" the it can be encoded as : "1"

encode("A") should return "1"
encode("BA") should return "21" as if mapping is a hash then B has a value of 2, A has a value of 1.
encode("ABC") should return "123" as mapping["A" is 1, mapping["B"] is 2, and mapping["C"] is 3.
encode("") should return "" as it is not in mapping.


Now if decode("1") is called then it should return an array with one element i.e. ["A"] as key matching with 1 as value in mapping is "A".
decode("") should return an array with empty string i.e. [""].
decode("21") should return an array ["BA", "U"] as 2 is "B", 1 is "A" and "U" is 21 in mapping.
decode("012") should return an empty array as string starts with "0" which is not in mapping keys.
decode("102") should return an array as ["JB"] as "10" is J and "2" is B.

最后 numDecode 应该返回数组中唯一解码字符串的计数。所以,

numDecode(X) == len(decode(X))
numDecode("1") == 1
numDecode("21") == 2
numDecode("123") == 3
numDecode("") == 1
numDecode("102") == 1
numDecode("012") == 0

【问题讨论】:

您的问题,示例代码要么有点混淆。尝试提高它的可读性和冗长性。 我理解这一点(并且投票赞成,因为它在数学上很有趣——它是一个产品系列,每个术语都来自斐波那契数列)。你说你“不能想出一个令人满意的解决方案”——也许如果你展示你尝试过的东西,有人可能会展示如何在 Ruby 中快速计算正确的值。考虑“121212”和“12912912”的编码可能会给你一个指针。 . . 不,它不是斐波那契数列。例子看起来像这样只是一个巧合。例如,decode("102") 应该给出 ["JB", "B"] 作为允许的组合,因为 "102" 可以拆分为 "10","2"。但是如果将其拆分为“1”、“0”、“2”,那么它不应该返回任何内容,因为“0”不在映射中。至少这是我从向我提出这个问题的人那里得到的解释。 +1 有趣的问题。两票否决??奇怪。像往常一样,没有提供任何解释或理由。 @user3075906:是的,这里有一个斐波那契数列。如果你看不到它,那就更努力地看。尝试字符串“1”、“12”、“121”、“1212”、“12121”等:-) 【参考方案1】:

这是一个有趣的问题,随之而来的面试技巧最有可能看出批判性思维能走多远。一个好的面试官可能不会期望一个典型的正确答案。

如果你得到一个递归的decode 解决方案,然后枚举,那么你在 IMO 上做得很好(至少我会雇用大多数候选人,他们可以在面试时通过一段递归代码清楚地展示思考!)

话虽如此,一个关键提示是问题要求num_decode 函数,不一定是encodedecode 的实现。

这里有更深入的理解和结构,可以通过分析排列和组合获得。它允许您编写一个num_decode 函数,该函数可以处理具有数百万个可能答案的长字符串,而无需填满内存或花费数小时来枚举所有可能性。

首先请注意,任何一组单独不明确的编码都会乘以整个字符串的可能性数量:

1920 -> 19 is ambiguous 'AI' or 'S' -> 'AIT' or 'ST'

192011 -> 11 is also ambiguous 'AA' or 'K' -> 'AITAA', 'AITK', 'STAA', 'STK'

这里19有两种可能的解释,11也有两种。具有这两种不明确编码的单独实例的字符串具有2 * 2 == 4 有效组合。

模糊编码的每个独立部分将整个解码值集的大小乘以它所代表的可能性的数量。

接下来如何处理更长的模棱两可的部分。当您将一个不明确的数字添加到一个不明确的序列时会发生什么:

11 -> 'AA' or 'K' -> 2
111 -> 'AAA', 'AK', 'KA' -> 3
1111 -> 'AAAA', 'AAK', 'AKA', 'KAA', 'KK' -> 5
11111 -> 'AAAAA', 'AAAK', 'AAKA', 'AKAA', 'AKK', 'KAAA', 'KAK', 'KKA' -> 8

2,3,5,8 应该很眼熟,是斐波那契数列,这是怎么回事?答案是向序列添加一位数字允许所有先前的组合加上之前子序列的组合。通过在序列1111 中添加一个数字1,您可以将其解释为1111(1)111(11) - 因此您可以将1111111 中的可能性数量相加得到可能性的数量在11111。即 N(i) = N(i-1) + N(i-2) 就是这样构造斐波那契数列的。

因此,如果我们可以检测到不明确的编码序列并获得它们的长度,我们现在可以计算可能解码的数量,无需实际进行解码

# A caching Fibonacci sequence generator
def fib n
  @fibcache ||= []
  return @fibcache[n] if @fibcache[n]
  a = b = 1
  n.times do |i|
    a, b = b, a + b
    @fibcache[i+1] = a
  end
  @fibcache[n]
end

def num_decode encoded
  # Check that we don't have invalid sequences, raising here, but you 
  # could technically return 0 and be correct according to question
  if encoded.match(/[^0-9]/) || encoded.match(/(?<![12])0/)
    raise ArgumentError, "Not a valid encoded sequence"
  end

  # The look-ahead assertion ensures we don't match
  # a '1' or '2' that is needed by a '10' or '20'
  ambiguous = encoded.scan(/[12]*1[789]|[12]+[123456](?![0])/)

  ambiguous.inject(1)  |n,s| n * fib(s.length) 
end

# A few examples:
num_decode('')  # => 1
num_decode('1') # => 1
num_decode('12') # => 2
num_decode('120') # => 1
num_decode('12121212') # => 34
num_decode('1212121212121212121212121211212121212121') # => 165580141

它是相对较短的字符串,例如试图枚举的最后一个字符串 直接通过解码的可能性。

scan 中的正则表达式进行了一些实验以使其正确。在1 之后添加789 是不明确的,但在2 之后则不是。您还希望避免将12 直接放在0 之前作为不明确序列的一部分,因为1020 没有其他解释。我想我在确定当前版本之前对正则表达式进行了大约十二次尝试(我认为这是正确的,但在我测试第一个版本的大多数时候,我确实一直在寻找更正值的异常)。

最后,作为一个练习,应该可以使用此代码作为编写解码器的基础,该解码器直接输出第 N 个可能的解码器(或者甚至是从任何起点懒惰地枚举它们的解码器,而不需要过多的内存或 CPU 时间)。

【讨论】:

这让我选为最佳答案。尼尔,如果你想换工作,你可能会和 SO 谈谈。我今天早上醒来,意识到零和对 p 的存在使得 p.to_i > 26 会将字符串划分为独立的子字符串,因此 num_decode 将是子字符串的 num_code 值的乘积,但我没有认识到斐波那契连接,直到你提到它。干得好! 再测试:num_decode('12'*50) # =&gt; 573147844013817084101.【参考方案2】:

这是一个递归解决方案:

$mapping = Hash[(0..25).map  |i| [('A'.ord+i).chr,i+1] ]
$itoa = Hash[$mapping.to_a.map  |pair| pair.reverse.map(&:to_s) ]

def decode( str )
  return [''] if str.empty?
  return $itoa.key?(str) ? [$itoa[str]] : nil if str.length == 1
  retval = []
  0.upto(str.length-1) do |i|
    word = $itoa[str[0..i]] or next
    tails = decode(str[i+1..-1]) or next
    retval.concat tails.map  |tail| word + tail 
  end
  return retval
end

一些示例输出:

p decode('1') # ["A"]
p decode('21') # ["BA", "U"]
p decode('123') # ["ABC", "AW", "LC"]
p decode('012') # []
p decode('') # [""]
p decode('102') # ["JB"]
p decode('12345') # ["ABCDE", "AWDE", "LCDE"]

注意此输出与问题之间的差异。例如。字母表的第 21 个字母是“U”,而不是“V”。等等

【讨论】:

【参考方案3】:
@he = Hash[("A".."Z").to_a.zip((1..26).to_a.map(&:to_s))]
                 # => "A"=>"1", "B"=>"2",...,"Z"=>"26" 
@hd = @he.invert # => "1"=>"A", "2"=>"B",.., "26"=>"Z"

def decode(str, comb='', arr=[])
  return arr << s if str.empty?

  # Return if the first character of str is not a key of @hd
  return arr unless (c = @hd[str[0]])

  # Recurse with str less the first char, s with c appended and arr
  arr = decode(str[1..-1], s+c, arr)

  # If the first two chars of str are a key of @hd (with value c),
  # recurse with str less the first two chars, s with c appended and arr
  arr = decode(str[2..-1], s+c, arr) if str.size > 1 && (c = @hd[str[0..1]])

  arr
end  

def num_decode(str) decode(str).size end

decode('1')        # => ["A"]
decode('')         # => [""].
decode('21')       # => ["BA", "U"]
decode('012')      # => [""]
decode('102')      # => ["JB"]
decode('123')      # => ["ABC", "AW", "LC"]
decode('12345')    # => ["ABCDE", "AWDE", "LCDE"]
decode('120345')   # => ["ATCDE"] 
decode('12720132') # => ["ABGTACB", "ABGTMB", "LGTACB", "LGTMB"]     

还有吗?是的,我看到后面有一只手。戴红帽子的绅士要见'12121212'

decode('12121212')
  # => ["ABABABAB", "ABABABL", "ABABAUB", "ABABLAB", "ABABLL",   "ABAUBAB",
        "ABAUBL",   "ABAUUB",  "ABLABAB", "ABLABL",  "ABLAUB",   "ABLLAB",
        "ABLLL",    "AUBABAB", "AUBABL",  "AUBAUB",  "AUBLAB",   "AUBLL",
        "AUUBAB",   "AUUBL",   "AUUUB",   "LABABAB", "LABABL",   "LABAUB",
        "LABLAB",   "LABLL",   "LAUBAB",  "LAUBL",   "LAUUB",    "LLABAB",
        "LLABL",    "LLAUB",   "LLLAB",   "LLLL"]

num_decode('1')        # =>  1
num_decode('21')       # =>  2
num_decode('12121212') # => 34
num_decode('12912912') # =>  8 

【讨论】:

谢谢。注意 num_decode("12121212") == 34 -> 对于一个 8 长的模糊序列,即位置 8 处的斐波那契序列。通常,对于每个长度为 N 的模糊序列,组合的数量是 Fib(N) - 做一个系列所有这些的产品,你有 num_decode 而无需实现解码。如果您认为没有必要,请在 '12' * 5000 上尝试您的解决方案! 谢谢,尼尔。我会记住你早上所说的关于斐波那契数列的内容。我现在没办法。我认为进行解码,而不仅仅是 num_decode,会很有趣,而且确实如此。【参考方案4】:

这看起来像一个组合问题,但它也是一个解析问题。

(你要求指点,所以我用英语来做这件事,而不是掸掉我的 Ruby。)

我会这样做:

    如果 X 为空字符串,则返回 1 如果 X 不是由以非零数字开头的数字组成的字符串,则返回 0 如果 X 不包含 1 或 2,则返回 1(只有一种可能的解析) 如果 X 包含 1 或 2,它会变得有点复杂:

不是 X 中最后一个字符的每个 1 都匹配“A”和字母“J”到“S”之一的第一个数字。

每一个不是 X 中最后一个字符且后面跟着一个小于 7 的数字的 2 都匹配“B”和其中一个字母的第一个数字。

数出满足这些条件的 1 和 2。假设这个数字是 Y。你有 2^Y 的组合,所以答案应该是 2^Y 但是你必须减去 1 每次你有一个 1 和 2 彼此相邻。

所以,如果您还没有通过上面的第 4 步返回,请计算不是 X 中最后一个字符的 1,以及都不是 X 中的最后一个字符且后面没有跟随的 2 7、8、9 或 10。将这些计数的总和称为 Y。

现在计算每个 1 和 2 是邻居的实例;让这个总和称为 Z。

可能的解析次数为 (2^Y) - Z。

【讨论】:

【参考方案5】:

本着给出“一些指示”的精神,与其为numDecode 编写一个实际的实现,让我说解决这个问题最符合逻辑的方法是递归。如果传递给numDecode 的字符串长于一个字符,则查看字符串的开头并根据您所看到的使用一两个(或零)递归调用来找到正确的值。

还有暴露太多的风险,numDecode("1122") 应该递归调用numDecode("122")numDecode("22")

【讨论】:

【参考方案6】:
# just look for all singles and double as you go down and keep repeating this.. if you get to the end where the string would be 1 or 2 digets long you count 1
# IE
# 121
# 1 that's good 2 that's good 1 that's good if all good then count + 1 
# 12 that's good 1 that's good ... no more doubles if all good then count + 1
# 1 that's good 21 that's good  if all good then count + 1 
# test this on other cases

$str = "2022"
$strlength = $str.length
$count = 0

def decode(str)
    if str[0].to_i >= 1 and str[0].to_i <= 9
        $count += 1 if str.length == 1
        decode(str[1..-1])
    end

    if str[0..1].to_i >= 10 and str[0..1].to_i <= 26
        $count += 1 if str.length == 2
        p str.length
        decode(str[2..-1])
    end
end



decode($str)

p " count is #$count"

【讨论】:

以上是关于使用 Ruby 查找给定编码字符串的组合的主要内容,如果未能解决你的问题,请参考以下文章

从给定字符串中查找长度为 k 的所有排列/组合

如何将 UTF8 组合字符转换为 ruby​​ 中的单个 UTF8 字符?

Ruby 正则表达式

在给定的字符串列表中查找字符串的所有字谜

如何使用 ruby​​ 1.9 转换字符编码

含有重复字符的字符串排列组合