【问题标题】:How to get the kth term in N Combination R [closed]如何获得N组合R中的第k项[关闭]
【发布时间】:2018-08-14 16:27:34
【问题描述】:

如何在NCR 中获得kth 组合。没有遍历所有可能的结果。例如假设我有3C2 用于3 职位和2identical-items。我知道它是[011][101][110]。我如何得到例如第二个术语(k=1)是[101] 使用方法?

约束R < N k >= 0k < P 其中P = NCR)。

注意:[101] 是第二项(按升序/字典顺序)因为011 = 3,101 = 5 ,110 = 6 十进制。所以基本上目标是得到 NCR 中的数字 k 是多少, 因为 NCR 的每个 kth 输出都可以表示为一个数字。

【问题讨论】:

  • N 和 R 的限制是什么,您能否也发布整个问题,这将有助于我们提出一种适用于您的情况的有效时间复杂度算法。
  • N 和 R 可以是任何数字,但 R k=3) 项:=> [0011],[0101],[0110],[1001],[1010],[1100] 等于[1001]
  • 哦,对 K 的约束是什么,比如 1
  • well k
  • 谈论“k-th”组合并没有真正的意义。有许多不同的集合具有 nCr 的基数并且它们没有排序。因此,您首先需要选择一个表示并在其上定义一个顺序。

标签: java math combinations permutation combinatorics


【解决方案1】:

是的,你说得对:

因为 NCR 的每 k 个输出都可以表示为一个数字。

从整数集1 to # of combs/perms 到整个梳子/烫发集之间存在双射。查找特定梳子/烫发的特定索引有时称为获取排名。根据您在问题中的示例,这些是普通排列。此外,当您提到升序时,您指的是lexicographical order

计算给定集合的nth个普通排列是一个简单的练习。我们首先需要使用完善的公式获得排列的总数:

P(n, r) = n! / (n - r)!

下一部分是关键观察,它使我们能够快速获得目标排列的每个元素。

如果我们查看我们的集合 n 的所有排列选择 r,将会有 n 个组仅在排列上有所不同n 个元素。

例如,如果我们看第一两组[0 1 2 3] choose 3的排列,我们有:

      [,0] [,1] [,2]
 [0,]    0    1    2
 [1,]    0    1    3
 [2,]    0    2    1
 [3,]    0    2    3
 [4,]    0    3    1
 [5,]    0    3    2
 [6,]    1    0    2
 [7,]    1    0    3
 [8,]    1    2    0
 [9,]    1    2    3
[10,]    1    3    0
[11,]    1    3    2

请注意,最后的排列只是集合[1 0 2 3] 的前 6 个排列。也就是说,0 映射到 1,1 映射到 0,最后 2 个元素映射到它们自己。

这种模式继续下去,因为我们只向右移动而不是 n 个相同的组,我们将在第二列获得 n - 1 个相似的组,n -2 第三个,依此类推。

所以要确定我们排列的第一个元素,我们需要确定 1st 组。我们通过简单地将排列数除以 n 来做到这一点。对于上面 4 选择 3 的排列示例,如果我们正在寻找 15th 排列,则第一个元素有以下内容:

Possible indices : [0 1 2 3]
P(4, 3) = 24
24 / 4 = 6 (elements per group)
15 / 6 = 2 (integer division) 2 means the 3rd element here (base zero)

现在我们已经使用了 3rd 元素,我们需要将它从我们的可能索引数组中删除。我们如何获得下一个元素?

很简单,我们通过从原始索引中减去我们刚刚找到的组和每组元素的乘积来获得下一个子索引

Possible indices : [0 1 3]
Next index is 15 - 6 * 2 = 3

现在,我们只需重复此操作,直到填写完所有条目:

Possible indices : [0 1 3]
Second element
6 / 3 = 2 (elements per group)
3 / 2 = 1
Next index is 3 - 3 * 1 = 0

Possible indices : [0 3]
Third element
2 / 2 = 1
0 / 1 = 0

所以我们的 15th 元素是:[2 1 0]

这是一个C++ 实现,应该很容易转换为Java

double NumPermsNoRep(int n, int k) {
    double result = 1;
    double i, m = n - k;

    for (i = n; i > m; --i)
        result *= i;

    return result;
}

std::vector<int> nthPermutation(int n, int r, double myIndex) {
    int j = 0, n1 = n;
    double temp, index1 = myIndex;
    std::vector<int> res(r);

    temp = NumPermsNoRep(n, r);
    std::vector<int> indexVec(n);
    std::iota(indexVec.begin(), indexVec.end(), 0);

    for (int k = 0; k < r; ++k, --n1) {
        temp /= n1;
        j = (int) std::trunc(index1 / temp);
        res[k] = indexVec[j];
        index1 -= (temp * (double) j);
        indexVec.erase(indexVec.begin() + j);
    }
}

这些概念扩展到其他类型的组合问题,例如找到第 nth 个组合,或重复排列等。

【讨论】:

    【解决方案2】:

    时间复杂度是O(kn),空间是O(n)

    public static void main(String[] args) {
        //n = 4, r = 2, k = 3
        int[] ret1 = getKthPermutation(4, 2, 3);
        //ret1 is [1,0,0,1]
    
        //n = 3, r = 2, k = 1
        int[] ret2 = getKthPermutation(3, 2, 1);
        //ret2 is [1,0,1]
    }
    
    static int[] getKthPermutation(int n, int r, int k) {
        int[] array = new int[n];
        setLastN(array, r, 1);
    
        int lastIndex = n - 1;
        for(int count = 0; count < k; count++) {
    
            int indexOfLastOne = findIndexOfLast(array, lastIndex, 1);
            int indexOfLastZero = findIndexOfLast(array, indexOfLastOne, 0);
            array[indexOfLastOne] = 0;
            array[indexOfLastZero] = 1;
    
            //shortcut: swap the part after indexOfLastZero to keep them sorted
            int h = indexOfLastZero + 1;
            int e = lastIndex;
            while(h < e) {
                int temp = array[h];
                array[h] = array[e];
                array[e] = temp;
                h++;
                e--;
            }
    
        }
    
        return array;
    }
    
    //starting from `from`, and traveling the array forward, find the first `value` and return its index.
    static int findIndexOfLast(int[] array, int from, int value) {
        for(int i = from; i > -1; i--)
            if(array[i] == value) return i;
        return -1;
    }
    
    //set the last n elements of an array to `value`
    static void setLastN(int[] array, int n, int value){
        for(int i = 0, l = array.length - 1; i < n; i++)
            array[l - i] = value;
    }
    

    这是对非常典型的“查找第 k 个排列”算法的改编。

    我将尝试解释一般概念(您的情况是一种特殊情况,因为只有两种类型的元素:0 和 1)。 假设我有[2,1,6,4,7,5]。比当前排列更大的下一个最小排列是什么?为什么我关心比当前更大的下一个最小排列?因为如果你从最小的排列[1,2,4,5,6,7] 开始,然后重复这个动作(找到比当前大的最小的)k 次,你会找到第 k+1 个最小的排列。

    现在,由于我要查找的值需要大于当前值,因此我需要增加当前值。为了使增量尽可能小,我将尝试修改 5(最后一个)。现在,我不能只将 5 更改为随机值,我只能将其与之前的某个数字交换。

    如果我将 5 换成更大的数字,比如 7,那么我将得到[2,1,6,4,5,7],它比当前的要小。现在显然我需要在它之前用一些较小的数字交换 5,但是哪一个呢?如果我用 2 交换 5,我得到[5,1,6,4,7,2],这个增量太大了。我需要用“低位”交换 5 以保持增量尽可能小。这导致我们找到小于 5 的第一个(最低)数字(从右到左)。在这种情况下,我需要将 5 与 4 交换并得到[2,1,6,5,7,4]。这样,我可以使“交换”的影响变小。现在前缀是[2,1,6,5。没有更小的前缀。我们需要处理后缀7,4]。显然,如果我们对后缀进行排序并使其成为4,7],那么我们就完成了。

    在我们的例子中,有两个区别: 1. 我们需要交换最后一个 1,因为你不能通过将一个零与它之前的任何数字交换来使排列变大。 2.我们总是可以使用代码所示的快捷方式对后缀进行排序。我会把它留给你:)

    【讨论】:

    • 哇!!!!......荣誉
    • 嗨@Wang Sheng,您能帮忙转换回(算法的反转)吗?谢谢
    • 你的意思是,给定一个排列并且它是第 k 个最小的排列,找到第 1 个最小的排列?
    • 不,我的意思是static int[] getKthPermutation(int n, int r, int k) 反转,即static int[] getK(int n, int r,int[] KthPermutation)
    • 在您的应用程序中,nr 是固定值吗?还是他们经常改变?给定相同的nr,你需要运行多少次反转功能?我问是因为我想知道你是否可以中间结果。
    【解决方案3】:
    public static String lexicographicPermutation(String str, long n) {
        final long[] factorials = { 1, 2, 6, 24, 120, 720, 5040, 40320, 362880, 3628800, 39916800, 479001600 };
    
        n--;
        char[] arr = str.toCharArray();
    
        for (int i = 0; i < arr.length - 1; i++) {
            long fact = factorials[arr.length - i - 2];
            long p = i + n / fact;
    
            n %= fact;
    
            for (int j = i + 1; j <= p; j++)
                swap(arr, i, j);
        }
    
        return new String(arr);
    }
    
    private static void swap(char[] arr, int i, int j) {
        char tmp = arr[i];
        arr[i] = arr[j];
        arr[j] = tmp;
    }
    

    您可以将STR 替换为所需的字符串。在给定的示例中,1st 排列是 "abcdefghijklm"(这是一个 13 个字符的字符串),13!st 排列是反向字符串 "mlkjihgfedcba"100st 排列是"abcfklgmeihjd"

    要实现这个解决方案,只需 google Factorial number system。这是解决此问题的关键。这是Project Euler: Problem 24

    演示:

    for(int i = 1; i <= 6; i++)
        System.out.println(lexicographicPermutation("110", i));
    
    1 - 110
    2 - 101
    3 - 110
    4 - 101
    5 - 011
    6 - 011
    
    for(int i = 1; i <= 6; i++)
        System.out.println(lexicographicPermutation("abc", i));
    
    1 - abc
    2 - acb
    3 - bac
    4 - bca
    5 - cab
    6 - cba
    

    【讨论】:

    • 投反对票,因为这不能解决问题,例如如果String STR = "110" 根据您的解决方案,我的问题的第 k 个术语始终是“110”,例如System.out.println(lexicographicPermutation(3));@main(String[] args)
    • @LiNKeR 看看我给的Demo。我的例子给出了绝对正确的答案。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-01-27
    • 1970-01-01
    • 2013-11-18
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多