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

时间:2013-12-06 20:29:20

标签: ruby string anagram

在接受采访时我被问到这个问题,我无法找到一个令人满意的解决方案。如果有人能给出一些指示,我将不胜感激。

给出类似

的映射
  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

6 个答案:

答案 0 :(得分:4)

这是一个有趣的问题,随之而来的采访技巧最有可能看出批判性思维的进展程度。一个好的面试官可能不会期望一个正统的正确答案。

如果你得到一个递归的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应该看起来很熟悉,这是斐波那契序列,发生了什么?答案是在序列中添加一个数字允许所有先前的组合加上之前的子序列。通过在序列1中添加数字1111,您可以将其解释为1111(1)111(11) - 这样您就可以将1111中的各种可能性加在一起和111获取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中的正则表达式进行了一些实验以使其正确。在7之后添加891不明确,但在2之后则不明确。您还希望避免在1之前直接计算20作为模糊序列的一部分,因为1020没有其他解释。在我确定当前版本之前,我认为我在正则表达式上进行了十几次尝试(我认为这是正确的,但我在测试第一个版本的时候,我确实一直在寻找正确值的例外)。

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

答案 1 :(得分:1)

这是一个递归解决方案:

$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”。等

答案 2 :(得分:1)

@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 

答案 3 :(得分:0)

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

(你问过指针,所以我用英文做这个,而不是把我的Ruby弄脏。)

我会做这样的事情:

  1. 如果X是空字符串,则返回1
  2. 如果X不是由以非零数字开头的数字组成的字符串,则返回0
  3. 如果X不包含1或2,则返回1(只有一种可能的解析)
  4. 如果X包含1或2,则会更复杂:
  5. 每个不是X中最后一个字符的1都匹配“A”和其中一个字母“J”到“S”的第一个数字。

    每两个不是X中的最后一个字符,后跟一个小于7的数字,则匹配“B”和其中一个字母的第一个数字。

    计算满足这些标准的1和2。让这个数字为Y.你有2 ^ Y组合,所以答案应该是2 ^ Y 但是每次你有1和2彼此相邻时你必须减去1。

    所以,如果你还没有通过上面的步骤4返回,那么计算你不是X中最后一个字符的1,以及2都不是X中的最后一个字符并且后面没有7,8,9或10.将这些计数的总和称为Y.

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

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

答案 4 :(得分:0)

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

显示过多的风险numDecode("1122")应该对numDecode("122")numDecode("22")进行递归调用。

答案 5 :(得分:0)

# 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}"