我想找到一个字符串的所有组合,维护顺序,但任何长度。例如:
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
答案 0 :(得分:4)
有两种方法可以在不使用Array#combination
的情况下完成。在允许combination
(#3) 1 的情况下,我还提供了案例代码。
<强> 1。将1
和2**n-1
(n
是字符串的长度)之间的每个数字映射到字符串中唯一的字符组合
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]
我们现在挑选wxyz
中bin_arr
的相应索引位置等于1
的每个字符,即
["w", "y", "z"]
然后将这些元素连接起来形成一个字符串:
["w", "y", "z"].join #=> "wyz"
由于每个数字1
到15
对应于此字符串中一个或多个字符的不同组合,因此我们可以通过对{{之间的每个数字重复上述计算来获得所有此类组合。 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
在这里,我们针对combos
和arr.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}},acd
,abd
)。这是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。