字符串字母的排列:如何去除重复排列?

Posted

技术标签:

【中文标题】字符串字母的排列:如何去除重复排列?【英文标题】:Permutation of String letters: How to remove repeated permutations? 【发布时间】:2011-08-02 19:57:41 【问题描述】:

这是一个打印字符串字符排列的标准函数:

void permute(char *a, int i, int n)

   int j;
   if (i == n)
     printf("%s\n", a);
   else
   
        for (j = i; j < n; j++) //check till end of string
       
          swap((a+i), (a+j));
          permute(a, i+1, n);
          swap((a+i), (a+j)); //backtrack
       
   
 

void swap (char *x, char *y)

    char temp;
    temp = *x;
    *x = *y;
    *y = temp;

它工作正常,但有一个问题,它还会打印一些重复的排列,例如:

如果字符串是“AAB”

输出是:

AAB
ABA
AAB
ABA
BAA
BAA

这也有 3 个重复条目。

有办法防止这种情况发生吗?

--

谢谢

阿洛克克。

【问题讨论】:

正在使用 std::set 不需要的开销? 听起来像是功课。如果是的话,你应该这样标记它。 先生,这不是家庭作业,我只是在研究一些标准算法,我遇到了这个问题。还要感谢 std::set 因为我在 c++ 中不太好所以不知道。 【参考方案1】:

记下您之前交换的字符:

 char was[256];
 /*
 for(j = 0; j <= 255; j++)
    was[j] = 0;
 */
 bzero(was, 256);
 for (j = i; j <= n; j++)
 
    if (!was[*(a+j)]) 
      swap((a+i), (a+j));
      permute(a, i+1, n);
      swap((a+i), (a+j)); //backtrack
      was[*(a+j)] = 1;
    
 

这必须是迄今为止参赛作品中最快的一个,在“AAAABBBCCD”(100 个循环)上进行了一些基准测试:

native C             - real    0m0.547s
STL next_permutation - real    0m2.141s

【讨论】:

你确定你是在比较同类吗?去掉打印后,让 STL 版本就地修改一个 char 数组,你的版本还快吗? 我不会删除打印,因为我不知道编译器会做什么优化。你可以用printf替换stream out,你是对的,这确实有很大的不同。另外,like vs like:我的代码只是 temporary 修改了 char 数组,最后它恢复了原始状态。 @Kumar:当然。你没看到逻辑吗?您的代码给出了重复项,因为它为每个位置使用了所有重复的字符。 我看到了逻辑,但是使用你的这段代码并没有给我正确的结果。我可能做错了什么,但你确定我不需要更改代码的任何其他部分 是否需要第二次交换?【参考方案2】:

标准库有你需要的:

#include <algorithm>
#include <iostream>
#include <ostream>
#include <string>
using namespace std;

void print_all_permutations(const string& s)

    string s1 = s;
    sort(s1.begin(), s1.end()); 
    do 
        cout << s1 << endl;
     while (next_permutation(s1.begin(), s1.end()));


int main()

    print_all_permutations("AAB");

结果:

$ ./a.out
AAB
ABA
BAA

【讨论】:

【参考方案3】:

另一种方法可能是:

    对数组进行预排序。

    这将确保所有重复项现在都是连续的。

    因此,我们只需要查看我们修复的前一个元素(并置换其他元素)

    如果当前元素与前一个元素相同,则不要置换。

【讨论】:

【参考方案4】:

我会这样做:首先,我生成字符“组”(即AABBBC 产生两个组:(AA) and (BBB) and (C)

首先,我们将AA 的所有分布迭代到n 字符上。对于找到的每个分布,我们将BBB 的所有分布迭代到n-2 剩余字符(未被A 占用)。对于涉及As 和Bs 的每个分布,我们将C 的所有分布迭代到剩余的空闲字符位置。

【讨论】:

我真的很喜欢这个,因为你根本不生成重复。 这也是我的想法。但是,实际实现它可能会变得有点麻烦。 不,我认为您甚至可以就地执行此操作,并且非常有效,如果您传递一个空插槽数组(如果您填充一个插槽,则将最后一个数组条目移动到相应的插槽在索引数组中)。【参考方案5】:

您可以使用std::set 来确保结果的唯一性。那就是如果它是 C++(因为你这样标记它)。

否则 - 手动检查结果列表并删除重复项。

当然,您必须保存结果并对其进行后处理,而不是像现在这样立即打印。

【讨论】:

谢谢,这会有所帮助,但我希望代码也能在 c 中工作,因为我也用 c 标记了它。所以另一种方式对我来说很好。 @Kumar - 是 C 还是 C++?使用 C 编译器编译时,C++ 代码可能无法工作,C 代码可能无法使用 C++ 编译器。编写 C 代码不会使其成为 C++,请自行决定使用哪种语言。【参考方案6】:

如果您只是认为这是一个需要存储所有排列以供将来使用的问题,那将非常简单。

所以你会有一个排列的字符串数组。

现在考虑一个新问题,这也是一个标准问题,您需要从数组中删除重复项。

希望对你有帮助。

【讨论】:

这将创建n! 排列,然后做一个非平凡的过滤器【参考方案7】:

@Kumar,我认为您想要的是以下内容:

#include <stdio.h>
#include <string.h>

/* print all unique permutations of some text. */
void permute(int offset, int* offsets, const char* text, int text_size)

    int i;

    if (offset < text_size) 
            char c;
            int j;

            /* iterate over all possible digit offsets. */
            for (i=0; i < text_size; i++) 
                    c=text[i];
                    /* ignore if an offset further left points to our
                       location or to the right, with an identical digit.
                       This avoids duplicates. */
                    for (j=0; j < offset; j++) 
                            if ((offsets[j] >= i) &&
                                (text[offsets[j]] == c)) 
                                    break;
                            
                    

                    /* nothing found. */
                    if (j == offset) 
                            /* remember current offset. */
                            offsets[offset]=i;
                            /* permute remaining text. */
                            permute(offset+1, offsets, text, text_size);
                    
            
     else 
            /* print current permutation. */
            for (i=0; i < text_size; i++) 
                    fputc(text[offsets[i]], stdout);
            
            fputc('\n', stdout);
    


int main(int argc, char* argv[])

    int i, offsets[1024];

    /* print permutations of all arguments. */
    for (i=1; i < argc; i++) 
            permute(0, offsets, argv[i], strlen(argv[i]));
    

    return 0;

此代码是 C,根据要求,它非常快并且可以执行您想要的操作。当然它包含可能的缓冲区溢出,因为偏移缓冲区具有固定大小,但这只是一个示例,对吧?

编辑:有人试过吗?有没有更简单或更快的解决方案?令人失望的是没有人进一步评论!

【讨论】:

【参考方案8】:
void permute(string set, string prefix = "")
    if(set.length() == 1)
            cout<<"\n"<<prefix<<set;
    
    else
            for(int i=0; i<set.length(); i++)
                    string new_prefix = prefix;
                    new_prefix.append(&set[i], 1);
                    string new_set = set;
                    new_set.erase(i, 1);
                    permute(new_set, new_prefix);
            
    

并简单地将其用作 permute("word");

【讨论】:

【参考方案9】:

不要在string的不同位置置换相同的字符。

在 Python 中:

def unique_permutation(a, l, r):
    if l == r:
        print ''.join(a)
        return
    for i in range(l, r+1):
        if i != l and a[i] == a[l]:
            continue
        a[i], a[l] = a[l], a[i]
        unique_permutation(a, l+1, r)
        a[i], a[l] = a[l], a[i]

【讨论】:

【参考方案10】:

算法步骤:

    将给定的字符串存储到临时字符串中,比如“temp” 从临时字符串中删除重复项 最后调用“void permute(char *a, int i, int n)”函数打印给定字符串的所有排列而不重复

我认为,这是最好和最有效的解决方案。

【讨论】:

这会影响结果的长度,这显然是错误的。

以上是关于字符串字母的排列:如何去除重复排列?的主要内容,如果未能解决你的问题,请参考以下文章

LeetCode——不同字符的最小子序列/去除重复字母

剑指Offer 38 - 字符串的排列

面试题 08.08. 有重复字符串的排列组合

面试题 08.08. 有重复字符串的排列组合

优化一个简单的缺失词算法[重复]

字符串的排列