查找字符串的所有组合,维护顺序,不固定长度的算法

时间:2016-02-22 23:49:04

标签: ruby algorithm

我想找到一个字符串的所有组合,维护顺序,但任何长度。例如:

string_combinations("wxyz")
# => ['w', 'wx', 'wxy', 'wxyz', 'wxz', 'wy', 'wyz', 'wz', 'x', 'xy', 'xyz', 'xz', 'y', 'yz', 'z']

我更喜欢你是否只能使用循环并避免使用像#combination这样的ruby方法,因为我试图找到最简洁的方法来实现它,如果我用另一种语言来看它。

有没有办法在低于O(n ^ 3)的情况下执行此操作?我最初的想法是这样的:

def string_combinations(str)
    result = []

    (0...str.length).each do |i|
        result << str[i]
        ((i+1)...str.length).each do |j|
            result << str[i] + str[j]
            ((j+1)...str.length).each do |k|
                result << str[i] + str[j..k]
                # Still not covering everything.
            end
        end
    end

    result
end

4 个答案:

答案 0 :(得分:4)

有两种方法可以在不使用Array#combination的情况下完成。在允许combination(#3) 1 的情况下,我还提供了案例代码。

<强> 1。将12**n-1n是字符串的长度)之间的每个数字映射到字符串中唯一的字符组合

def string_combinations(str)
  arr = str.chars
  (1..2**str.length-1).map do |n|
    pos = n.bit_length.times.map.with_object([]) { |i,a| a << i if n[i] == 1 }
    arr.values_at(*pos).join
  end.sort
end

string_combinations("wxyz")
  # => ["w", "wx", "wxy", "wxyz", "wxz", "wy", "wyz", "wz",
  #     "x", "xy", "xyz", "xz", "y", "yz", "z"] 

离散概率理论为我们提供了等式

sum(i = 1 to n) ( |i| C(n,i) ) == 2^n - 1

其中C(n,i)是“我一次取n件事的组合数”。

如果给定字符串为"wxyz"n = "wxyz".length #=> 4,则此字符串中包含一个或多个字符的2**4 - 1 #=> 15个组合。现在考虑1到16之间的任何数字,比如11,二进制是0b1011。将其转换为二进制数字数组,我们得到:

bin_arr = [1,0,1,1]

我们现在挑选wxyzbin_arr的相应索引位置等于1的每个字符,即

["w", "y", "z"]

然后将这些元素连接起来形成一个字符串:

["w", "y", "z"].join #=> "wyz"

由于每个数字115对应于此字符串中一个或多个字符的不同组合,因此我们可以通过对{{之间的每个数字重复上述计算来获得所有此类组合。 1}}和1

无论您使用哪种方法,结果数组都会包含15个元素,因此您正在查看2**n - 1

<强> 2。使用递归

O(2**str.length)

请注意

def string_combinations(str)
  (combos(str) - [""]).sort
end

def combos(str)
  return [str, ""] if str.length==1
  forward = combos str[1..-1]
  [*forward, *[str[0]].product(forward).map(&:join)]
end

string_combinations("wxyz")
  # => ["w", "wx", "wxy", "wxyz", "wxz", "wy", "wyz", "wz",
  #     "x", "xy", "xyz", "xz", "y", "yz", "z"] 

包含一个空字符串,必须将其删除,并且数组需要排序。因此需要分离出递归方法combos("wxyz") #=> ["z", "", "yz", "y", "xz", "x", "xyz", "xy", # "wz", "w", "wyz", "wy", "wxz", "wx", "wxyz", "wxy"]

第3。使用Array#combination

在这里,我们针对combosarr.combination(n)之间的n的所有值调用1,并返回由所有arr.size返回值组成的(展平的)数组。

n

1因为我在认识到这不是OP想要的之前写了它。 ¯\ _(ツ)_ /¯

答案 1 :(得分:1)

使用堆栈的一个非常简单的解决方案(我不能提供ruby代码):

string inp
list result

//initialize stack
stack s
s.push(0)

while(!s.isEmpty())
    int tmp = s.peek()

    //the current value is higher than the max-index -> shorten prefix
    if tmp >= inp.length()
        s.pop()

        //increment the last character of the prefix
        if !s.isEmpty()
            s.push(s.pop() + 1)

        continue

    //build the result-string from the indices in the stack
    //note that the indices in the stack are reverse (highest first)!!!
    result.add(buildString(inp , s)

    //since we aren't at the end of the string, we can append another character to the stack
    s.push(tmp + 1)

基本思想是保持一系列将从中获取角色的位置。此堆栈具有以下属性:堆栈中的每个元素都比堆栈中的下一个元素大。因此保持了堆栈的排序。如果我们达到一个等于字符串长度的数字,我们就会消除该数字并增加下一个数字,从而转移到下一个前缀。

E.g.:
stack                                    string
0 (init)                                 a b c (init)
0                                        a
0 1                                      a b
0 1 2                                    a b c
0 2                                      a c
1                                        b
1 2                                      b c
2                                        c

堆栈的查看将表示已修改的输入字符串的字符,堆栈的其余部分表示前缀。

答案 2 :(得分:1)

似乎算法可以用英语表示为:

"w", followed by "w" + all combinations of "xyz", followed by
"x", followed by "x" + all combinations of "yz", followed by
etc.

换句话说,有一个“前缀”的概念,然后在“剩余的字符”上递归。考虑到这一点,这是一个Ruby解决方案:

def combine_with_prefix(prefix, chars)
  result = []
  chars.each_with_index do |ch, i|
    result << "#{prefix}#{ch}"
    result.concat(combine_with_prefix(result.last, chars[(i + 1)..-1]))
  end
  result
end

def string_combinations(str)
  combine_with_prefix(nil, str.chars)
end

string_combinations("wxyz")
# => ["w", "wx", "wxy", "wxyz", "wxz", "wy", "wyz", "wz", "x", "xy", "xyz", "xz", "y", "yz", "z"]

答案 3 :(得分:1)

这是考虑这个问题的另一种方式:首先是输入字符串 s ,其长度为 n 。从中您可以通过从 s 中删除一个字符来计算可以生成的字符串集。该套装有 n 成员。对于每个成员,您执行相同的操作:计算可以通过删除一个字符生成的字符串集。每个集合都有 n-1 成员。对于每个 n(n-1)成员,再次执行操作,依此类推,直到 n 为1.结果是所有计算集合的并集

例如,假设您的起始字符串为abcd n = 4)可以通过删除一个字符生成的字符串集是(bcd,{{ 1}},acdabd)。这是4个操作。对每个字符串重复操作产生4组3个成员(4x3 = 12个操作),每个成员具有长度2.重复再次产生12组,每组2个成员(4x3x2 = 24个操作),每个具有长度1。这是一个神奇的数字,所以我们将所有这些字符串整理出来,抛弃重复数据,我们得到了答案。最后我们做了4 + 4x3 + 4x3x2 = 40次操作。

对于每个字符串长度都适用。如果我们有5个字符,我们做5 + 5x4 + 5x4x3 + 5x4x3x2 = 205操作。对于6个字符,它是1,236个操作。我留给你找出big- O 符号中的等价物。

这归结为一个非常简单的递归算法:

abc

我们最终会与def comb(str) [ str, *if str.size > 1 str.each_char.with_index.flat_map do |_,i| next_str = str.dup next_str.slice!(i) comb(next_str) end end ] end p comb("wxyz").uniq.sort # => [ "w", "wx", "wxy", "wxyz", "wxz", "wy", "wyz", "wz", # "x", "xy", "xyz", "xz", "y", "yz", "z" ] 一起抛出很多东西,这告诉我们通过记忆可以节省很多周期:

uniq

如果您感到好奇,通过记忆,对于4个字符的输入,内循环达到23次,而没有记忆的则为41; 5个字符共46对206次; 8对比为87对1,237次;我认为,相当重要的是162和8,653。