问题是:给定一组可能包含重复项的数字,返回所有唯一排列。
天真的方法是使用一组(在C ++中)来保存排列。这需要 O ( n !×log( n !))时间。有更好的解决方案吗?
答案 0 :(得分:4)
最简单的方法如下:
O(n lg n)
O(n! * <complexity of finding next permutaion>)
步骤3可以通过将下一个排列定义为在排列列表排序后直接出现在当前排列之后的排列来实现,例如:
1, 2, 2, 3
1, 2, 3, 2
1, 3, 2, 2
2, 1, 2, 3
2, 1, 3, 2
2, 2, 1, 3
...
查找下一个词典排列是O(n),并在标题Generation in lexicographic order下的维基百科页面上进行简单描述。 如果您有野心,可以使用plain changes
在O(1)中生成下一个排列答案 1 :(得分:2)
1)回溯/递归搜索的一些变化通常会解决这类问题。给定一个返回(n-1)个对象上所有排列的列表的函数,生成n个对象上所有排列的列表,如下所示:对于列表中的每个元素,在所有可能的位置插入第n个对象,检查重复项。这不是特别有效,但它通常会为这类问题生成简单的代码。
2)参见维基百科http://en.wikipedia.org/wiki/Permutation#Generation_in_lexicographic_order
3)学者们花了很多时间来详细说明这一点。参见Knuth Vol 4A第7.2.1.2节 - 这是一本大型精装书,其中包含以下亚马逊的简要目录:
第7章:组合搜索1
7.1:Zeros and Ones 47
7.2:产生所有可能性281
答案 2 :(得分:1)
你应该阅读my blog post这种排列(以及其他内容)以获得更多背景 - 并关注其中的一些链接。
这是我的Lexicographic排列生成器的一个版本,它按照Steinhaus-Johnson-Trotter排列生成器的生成顺序生成,按要求执行:
def l_perm3(items):
'''Generator yielding Lexicographic permutations of a list of items'''
if not items:
yield []
else:
dir = 1
new_items = []
this = [items.pop()]
for item in l_perm3(items):
lenitem = len(item)
try:
# Never insert 'this' above any other 'this' in the item
maxinsert = item.index(this[0])
except ValueError:
maxinsert = lenitem
if dir == 1:
# step down
for new_item in [item[:i] + this + item[i:]
for i in range(lenitem, -1, -1)
if i <= maxinsert]:
yield new_item
else:
# step up
for new_item in [item[:i] + this + item[i:]
for i in range(lenitem + 1)
if i <= maxinsert]:
yield new_item
dir *= -1
from math import factorial
def l_perm_length(items):
'''\
Returns the len of sequence of lexicographic perms of items.
Each item of items must itself be hashable'''
counts = [items.count(item) for item in set(items)]
ans = factorial(len(items))
for c in counts:
ans /= factorial(c)
return ans
if __name__ == '__main__':
n = [0, 1, 2, 2, 2]
print '\nLexicograpic Permutations of %i items: %r' % (len(n), n)
for i, x in enumerate(l_perm3(n[:])):
print('%3i %r' % (i, x))
assert i+1 == l_perm_length(n), 'Generated number of permutations is wrong'
以上程序的输出如下:
Lexicograpic Permutations of 5 items: [0, 1, 2, 2, 2]
0 [0, 1, 2, 2, 2]
1 [0, 2, 1, 2, 2]
2 [2, 0, 1, 2, 2]
3 [2, 0, 2, 1, 2]
4 [0, 2, 2, 1, 2]
5 [2, 2, 0, 1, 2]
6 [2, 2, 0, 2, 1]
7 [0, 2, 2, 2, 1]
8 [2, 0, 2, 2, 1]
9 [2, 2, 2, 0, 1]
10 [2, 2, 2, 1, 0]
11 [2, 1, 2, 2, 0]
12 [1, 2, 2, 2, 0]
13 [2, 2, 1, 2, 0]
14 [2, 2, 1, 0, 2]
15 [1, 2, 2, 0, 2]
16 [2, 1, 2, 0, 2]
17 [2, 1, 0, 2, 2]
18 [1, 2, 0, 2, 2]
19 [1, 0, 2, 2, 2]
答案 3 :(得分:0)
这是我在思考如何手工编写排列并将该方法放入代码后发明的更短更好:
def incv(prefix,v):
list = []
done = {}
if v:
for x in xrange(len(v)):
if v[x] not in done:
done[v[x]] = 1
list = list + incv(prefix+v[x:x+1],v[:x] + v[x+1:])
else:
list.append(''.join(prefix))
return list
def test(test_string,lex_ord=False):
if lex_ord:
test_string = [x for x in test_string]
test_string.sort()
p = incv([],[x for x in test_string])
if lex_ord:
try_p = p[::]
try_p.sort()
print "Sort methods equal ?", try_p == p
print 'All', ','.join(p), "\n", test_string, "gave", len(p), "permutations"
if __name__ == '__main__':
import sys
test(sys.argv[1],bool(sys.argv[2] if len(sys.argv) > 2 else False))
备注强>
incv
递增置换向量以找到所有这些。它还可以正确处理重复的字母。test
打印出所有排列及其对测试字符串的计数。它还确保如果您请求字典顺序排序方法之前和排序方法是相同的。这应该是True,因为原始字符串是有序的,并且增量置换函数将字符串转换为给定字母表的下一个字典字符串。此脚本可以在命令提示符下运行:
python script.py [test_string] [optional anything to use lexicographic ordering]
答案 4 :(得分:0)
我略微改进了Paddy3118's solution,所以它现在是非递归的,懒惰评估的(完全基于生成器)并且大约快30%。
def _handle_item(xs, d, t):
l = len(xs)
try:
m = xs.index(t)
except ValueError:
m = l
if d:
g = range(l, -1, -1)
else:
g = range(l + 1)
q = [t]
for i in g:
if i <= m:
yield xs[:i] + q + xs[i:]
def _chain(xs, t):
d = True
for x in xs:
yield from _handle_item(x, d, t)
d = not d
def permutate(items):
xs = [[]]
for t in items:
xs = _chain(xs, t)
yield from xs
P.S。我注意到Paddy3118已经使他的实现使用了生成器,而我一直在反对博客文章中的实现,这更加内存。无论如何,我发布这个版本,因为这个版本可能被认为更干净。
答案 5 :(得分:0)
递归版。这计算n!/(m * k!)(m组的字符数,每组中k个重复的字符数:
#include<iostream>
#include<cstring>
using namespace std;
const int MAX_CHARS_STRING=100;
int CURRENT_CHARS=0;
char STR[MAX_CHARS_STRING];
void myswap(int i, int j){
char c=STR[i];STR[i]=STR[j];STR[j]=c;
}
bool IstobeExecuted(int start,int position){
if(start==position)
return true;
for(int i=position-1;i>=start;i--){
if(STR[i]==STR[position])
return false;
}
return true;
}
void Permute(int start, int end,int& perm_no){
if(end-start<=1){
if(STR[end]==STR[start]){
cout<<perm_no++<<") "<<STR<<endl;
return;
}
cout<<perm_no++<<") "<<STR<<endl;
myswap(start, end);
cout<<perm_no++<<") "<<STR<<endl;
myswap(start,end);
return;
}
for(int i=start; i<=end;i++){
if(!IstobeExecuted(start,i)){
continue;
}
myswap(start,i);
Permute(start+1,end,perm_no);
myswap(start,i);
}
}
int main(){
cin>>STR;int num=1;
Permute(0,strlen(STR)-1,num);
return 0;
}
希望这有帮助
答案 6 :(得分:0)
@verdesmarald解决方案的简单且简短的C ++实现:
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<vector<int>> res;
const auto begin = nums.begin();
const auto end = nums.end();
std::sort(begin, end);
do
{
res.push_back(nums);
}
while (std::next_permutation(begin, end));
return res;
}
我认为时间复杂度是:n * log(n)+ m * ComplexityOf(next_permutation) 其中n是元素总数,m是唯一元素,next_permutation的复杂度摊销O(1)。 或因此,他们说:The amortized complexity of std::next_permutation?