迭代地找到字符数组k的所有组合(N选择K)

时间:2015-07-02 04:16:10

标签: algorithm iteration combinations permutation variable-length

我目前正在将此问题作为个人项目处理。

基本上:

  • 给出一系列元素,例如E = {1,2,a,b}和
  • 给出一个数字,K,例如K = 2
  • 我想要返回K大小的所有Combinations(E选择K)
  • E.g。 {{1,1},{1,2},{1,a},{1,b},{2,1},...,{b,1},{b,2},{b, a},{b,b}}

我已经使用以下函数递归地实现了这个:

char[] pool = new char[]{'1', '2', '3'};

public void buildStringRec(char[] root, int pos, int length){
    for(char c : pool){
        char[] newRoot = root.clone();
        newRoot[pos] = c;
        if(pos+1 < length){
            buildStringRec(newRoot, pos+1, length);
        } else{
            System.out.println(String.valueOf(root));
        }
    }
}

pool为E且length为K。

所以我们打电话给:buildStringRec(new char[2], 0, 2);并获取

11
12
13
21
22
23
31
32
33

这可以迭代完成吗?我一直试图围绕如何使用可变长度来做这件事。

任何帮助将不胜感激!如果需要,我可以按照原样发布我的代码,但由于我的重试,它发生了变化,一旦发布它几乎没用。

另外,我不想使用Apache或String Builder这样做,因为我想了解如何操作的概念。我不是简单地要求代码。伪代码只要清楚地解释就可以了。

谢谢!

修改

我正在使用此网站测试呈现给我的所有选项:https://ideone.com/k1WIa6
随意分叉并尝试一下!

4 个答案:

答案 0 :(得分:4)

递归解决方案

至于我,看起来递归解决方案是最好的选择。
您可以使用堆栈替换克隆路径数组以提高性能:

char[] pool = new char[]{'1', '2', '3'};
Stack<int> stack = new Stack<int>();

// Actually, resulting length should be the same length as pool array
int length = pool.length;

public void buildStringRec(int pos)
{
    if (length == pos + 1) 
    {
        System.out.println(String.valueOf(root));
        return;
    }

    for(char c : pool){
        stack.Push(c);
        buildStringRec(pos + 1);
        stack.Pop(c);
    }
}

迭代解决方案

我们假设,出于某种原因,您需要迭代地执行此操作 我相信有更好的解决方案。但是,这是我能做的最好的事情。

您可以将您的任务改写为另一个任务:

  

如何输出长度为N的基数N的所有数字。

假设您有一个长度为3的数组:{'a', 1, 'z'} 你理想的答案是:

a-a-a    a-a-1    a-a-z
a-1-a    a-1-1    a-1-z
a-z-a    a-z-1    a-z-z
1-a-a    1-a-1    1-a-z

现在,让我们看一下这些值的指数:

0-0-0    0-0-1    0-0-2
0-1-0    0-1-1    0-1-2
0-2-0    0-2-1    0-2-2 
2-0-0    2-0-1    2-0-2

实际上,这是基数3中的连续数字:000, 001, 002, 010, 011, 012, 020, 021, 022, 200, 201, 202

请记住他们的计数公式:base ^ length。在我们的案例中,length == base。因此,它是base ^ base

现在,我们的任务变得更加容易:

int[] toBase(long bs, long value)
{ 
    int[] result = new int[bs];
    for (long i = bs - 1; i >= 0; i--)
    {
        result[i] = (int)(value % bs);
        value = value / bs;
    }
    return result;
}

long Pow(long a, long b)
{
    long result = 1;
    for (int i = 0; i < b; i++) result *= a;
    return result;
}

char[] pool = new char[] {'a', 'b', 'c'};

void outputAll()
{
    long n = pool.Length;
    for (long i = 0; i < Pow(n, n); i++)
    {
        int[] indices = toBase(n, i);
        for (int j = 0; j < n; j++)
            Console.Write("{0} ", pool[indices[j]]);
        Console.WriteLine();
    }
}

当然,可能会有一些优化:

  • 每次都无需计算toBase。从000开始并且每次计算下一个数字都更容易且更高效。
  • 您可以更改Pow功能以使用快速exponentiation by squaring算法等。

这只是一个解释方法的例子。

请记住,长度为3的数组只有27个这样的组合。但是,长度为7的数组将具有823543.它以指数方式增长。

工作样本

这是DotNetFiddle working Demo

只需更改pool数组值即可获得结果。 在这里和上面的所有例子中,我都使用了C#。它可以很容易地转换为C ++:)

至于我,它的长度可达7(约1 - 1.5秒) 当然,您需要删除控制台输出才能获得此类结果。控制台输出非常慢

答案 1 :(得分:3)

这是另一个迭代解决方案:

您可以创建一个大小为K的整数数组作为计数器记录您通过组合的距离,以及一个用于存储当前组合的字符数组。

打印完每个后,通过增加其中一个计数器值继续下一个组合,如果通过达到等于E中元素数的值“溢出”,则将其重置为零并执行进位通过在下一个位置递增计数器,检查那里的溢出等等。有点像车里的里程表,除了数字与E中的值相关联。一旦最后一个位置溢出,那么你已经产生了所有可能的组合。

我从数组中的最后一个值开始递增计数器并向下移动以获得与示例中相同的输出,但这当然不是必需的。该算法不检查重复项。

您不必使用当前组合存储字符数组,您可以在每次基于计数器的for循环中重新生成字符,但效率可能较低。此方法仅更新更改的值。

public static void buildStrings(char[] root, int length)
{
    // allocate an int array to hold the counts:
    int[] pos = new int[length];
    // allocate a char array to hold the current combination:
    char[] combo = new char[length];
    // initialize to the first value:
    for(int i = 0; i < length; i++)
        combo[i] = root[0];

    while(true)
    {
        // output the current combination:
        System.out.println(String.valueOf(combo));

        // move on to the next combination:
        int place = length - 1;
        while(place >= 0)
        {
            if(++pos[place] == root.length)
            {
                // overflow, reset to zero
                pos[place] = 0;
                combo[place] = root[0];
                place--; // and carry across to the next value
            }
            else
            {
                // no overflow, just set the char value and we're done
                combo[place] = root[pos[place]];
                break;
            }
        }
        if(place < 0)
            break;  // overflowed the last position, no more combinations
    }
}

ideone.com demo

答案 2 :(得分:3)

一个简单的迭代解决方案是

  • 创建一个数组来保存每个字符的索引以获得所需的输出长度
  • 然后遍历索引并打印与索引相对应的池中的每个字符
  • 然后递增索引数组的最后一个索引
  • 如果最后一个索引等于池长度
    • 将其设为零
    • 增加先前的索引
    • 使用上一个索引重复,直到到达数组的开头或索引不等于池长度

以下是Java中的一些示例代码

char[] pool = new char[]{'1', '2', '3'};

public void buildStrings(int length){

  int[] indexes = new int[length];
  // In Java all values in new array are set to zero by default
  // in other languages you may have to loop through and set them.

  int pMax = pool.length;  // stored to speed calculation
  while (indexes[0] < pMax){ //if the first index is bigger then pMax we are done

    // print the current permutation
    for (int i = 0; i < length; i++){
      System.out.print(pool[indexes[i]]);//print each character
    }
    System.out.println(); //print end of line

    // increment indexes
    indexes[length-1]++; // increment the last index
    for (int i = length-1; indexes[i] == pMax && i > 0; i--){ // if increment overflows 
      indexes[i-1]++;  // increment previous index
      indexes[i]=0;   // set current index to zero  
    }     
  }
}

答案 3 :(得分:0)

迭代解决方案

这是我用C / C ++开发的算法,具有迭代函数和恒定的空间复杂度

它处理C数组的索引,因此它可以用于任何类型的数据,这里是一个字符数组(C ++字符串),可以是一串数字

功能1:生成所有可能的组合

// str: string of characters or digits
void GenerateAll(string str, int k)
{
    int n = str.length();

    // initialization of the first subset containing k elements
    int *sub_tab = new int[k];
    for(int j(0); j<k; ++j)
    {
        sub_tab[j] = j;
    }

    do
    {   
        // Convert combination to string
        char *sub_str = new char[k];
        for(int j(0); j<k; ++j)
        {
            sub_str[j] = str[sub_tab[j]];
        }
        // Print combinations of each set
        Combinations(sub_str);
        // get next sub string
    } while (AddOne(sub_tab, k-1, n) == true);

    delete [] sub_tab;      
}

功能2:为每组生成所有组合

void Combinations(string str)
{
    int n = str.length();

    // Compute all factorials from 0 to n
    unsigned long int * factorials = new unsigned long int[n+1];
    factorials[0] = 1;
    for(int i = 1; i<=n; ++i)
        factorials[i] = factorials[i-1] * i;

    char *tab = new char[n];

    // Initialization with the first combination 0123...n-1
    for(int i(0); i<n; ++i)
    {
        tab[i] = i;
        cout << str[i] << " ";
    }
    cout << endl;

    for(unsigned long int i(1); i < factorials[n]; ++i)
    {
        for (int j(0); j < n; ++j)
        {
            if(i % factorials[n-j-1] == 0)
            {
                // increment tab[j] (or find the next available)
                SetNextAvailable(tab, j, n);
            }
        }
        for (int j(0); j < n; ++j)
        {
            cout << str[tab[j]] << " "; 
        }
        cout << endl;
    }

    delete [] factorials;
}

功能SetNextAvailable()

void SetNextAvailable(char *tab, int j, int n)
{
    bool finished;
    do
    {
        finished = true;
        ++(*(tab+j));
        if (*(tab+j) == n) *(tab+j) = 0;
        for (int i(0); i < j; ++i)
        {
            if ( *(tab+i) == *(tab+j) )
            {
                finished = false;
                break;
            }
        }
    } while(finished == false);
}

功能AddOne()

bool AddOne(int *tab, int k, int n)
{
    int i;
    for(i=k; i>=0; --i)
    {
        if(((++tab[i]) + (k-i)) != n)
            break;
    }
    if(i == -1)
        return false;
    else
    {
        for(int j=i+1; j<=k; ++j)
        {
            tab[j] = tab[j-1] + 1;
        }
        return true;
    }
}