列出所有唯一数字排列的算法包含重复项

时间:2012-07-11 03:21:12

标签: algorithm permutation combinations

问题是:给定一组可能包含重复项的数字,返回所有唯一排列。

天真的方法是使用一组(在C ++中)来保存排列。这需要 O n !×log( n !))时间。有更好的解决方案吗?

7 个答案:

答案 0 :(得分:4)

最简单的方法如下:

  1. 对列表进行排序:O(n lg n)
  2. 排序列表是第一个排列
  3. 重复生成前一个“O(n! * <complexity of finding next permutaion>)
  4. 的”下一个“排列

    步骤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?