Algorithm 列出包含重复项的所有唯一数字排列的算法
问题是:给定可能包含重复项的数字集合,返回所有唯一排列Algorithm 列出包含重复项的所有唯一数字排列的算法,algorithm,permutation,combinations,Algorithm,Permutation,Combinations,问题是:给定可能包含重复项的数字集合,返回所有唯一排列 最简单的方法是使用一个集合(在C++中)来保存排列。这需要O(n!×log(n!)时间。有更好的解决方案吗?最简单的方法如下: 对列表排序:O(n lg n) 排序列表是第一个排列 从上一个排列重复生成“下一个”排列:O(n!*) 步骤3可以通过定义下一个排列来完成,如果排列列表已排序,则下一个排列将直接出现在当前排列之后,例如: 1, 2, 2, 3 1, 2, 3, 2 1, 3, 2, 2 2, 1, 2, 3 2, 1, 3, 2
最简单的方法是使用一个集合(在C++中)来保存排列。这需要O(n!×log(n!)时间。有更好的解决方案吗?最简单的方法如下:
O(n lg n)
O(n!*)
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),在Wikipedia页面的标题下给出了排列的简单描述。
如果您有雄心壮志,可以使用1)在O(1)中生成下一个排列。回溯/递归搜索的一些变体通常可以解决这类问题。给定一个函数来返回(n-1)个对象上所有排列的列表,生成n个对象上所有排列的列表,如下所示:对于列表中的每个元素,在所有可能的位置插入第n个对象,检查重复项。这不是特别有效,但它通常会为这类问题生成简单的代码
2) 见维基百科
3) 学者们花了大量时间研究这方面的细节。参见Knuth第4A卷第7.2.1.2节-这是一本大型精装书,亚马逊上有以下简要目录:
第7章:组合搜索1
7.1:零和一47
7.2:生成所有可能性281你应该阅读这种排列(除其他外)以获得更多背景信息,并遵循其中的一些链接
这是我的字典排列生成器的一个版本,它是根据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'
这是我在思考如何手工写出排列并将该方法编入代码后发明的,它更短、更好:
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
打印测试字符串的所有排列及其计数。它还确保如果您请求字典排序,那么sort before和sort after方法是相同的。这应该是正确的,因为原始字符串是有序的,增量置换函数会根据给定的字母表将字符串转换为下一个词典字符串test
python script.py[test_string][optional any to use lexicalographic ordering]
我稍微改进了一下,所以它现在是非递归的、延迟计算的(完全基于生成器),速度大约提高了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
def_handle_项目(xs、d、t):
l=len(xs)
尝试:
m=xs.索引(t)
除值错误外:
m=l
如果d:
g=范围(l,-1,-1)
其他:
g=范围(l+1)
q=[t]
对于i in g:
如果我一个递归版本。这算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;
}
#包括
#包括
使用名称空间std;
常量int MAX_CHARS_STRING=100;
int当前字符=0;
字符STR[MAX_CHARS_STRING];
void myswap(int i,int j){
字符c=STR[i];STR[i]=STR[j];STR[j]=c;
}
bool ISTOBECTED已执行(int start,int position){
如果(开始==位置)
返回true;
对于(int i=位置-1;i>=开始;i--){
if(STR[i]==STR[position])
返回false;
}
返回true;
}
无效排列(整数开始、整数结束、整数和排列编号){
如果(结束启动< P>)@ VordSMALD的解决方案的简单和简短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;
}
vector permuteUnique(vector&nums){
向量res;
const auto begin=nums.begin();
const auto end=nums.end();
排序(开始、结束);
做
{
res.推回(nums);
}
while(std::next_置换(begin,end));
返回res;
}
我认为时间复杂度是:n*log(n)+m*ComplexityOf(next_置换)
其中n是元素总数,m是唯一元素,下一个_置换的复杂性是O(1)摊销。
或者他们是这样说的:既然有n!
不同整数的排列,你就不能比O(n!)做得更好了如果需要枚举它们,也请注意,重复的存在是不相关的,因为删除重复的过程与枚举排列相比,花费的时间可忽略不计。@ VelDeMalald。是的,我试图减少O(n)!1的时间复杂度。<代码> NExtx置换> /Calp>(C++中的STL)即使存在重复项,也只访问每个置换一次。2.仅空间要求是O(nn!),而不是O(n!)。3.在STL集中插入所有n!置换将需要O(n!log(n!))=O(nn!*logn)@bloops我相信练习的重点是实现next\u permutation
。此外,我可能应该限定我所说的只是时间复杂性,我只会将它们存储在一个列表中(因为下一个perm算法已经排除了重复项)@bloops.1.下一个置换会让问题变得不那么有趣。2.你是对的,使用set的总体时间复杂度是O(n!lg(n!)。更新:我最初误解了这个问题,认为在计算置换之前应该丢弃重复的。下一个置换就是重点。夏天没有作业:)@zwx我住的地方,现在是冬天。:)我添加了一些到下一个perm算法的链接,如果您遇到任何具体困难,我很乐意提供帮助,但否则我看不到我在这里重新键入算法的任何优点。“常规”next\u的复杂性
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;
}