求组合数

很多问题都是从一个基础问题衍生而出的,求组合数的基础问题就是求子集。这里介绍生成子集的两种方法:位向量法,二进制法。

void subset(int[] arr, boolean[] path, int cur) {
    if (arr == null || arr.length == 0 || path == null) {
        return;
    }
    if (cur == arr.length) {
        for (int i = 0; i < path.length; i++) {
            if (path[i]) {
                System.out.print(arr[i] + " ");
            }
        }
        System.out.println();
        return;
    }

    path[cur] = false;
    subset(arr, path, cur + 1);
    path[cur] = true;
    subset(arr, path, cur + 1);
}

位向量法的本质是遍历解答树,复杂度是O(n*2^n)。

void subset(int[] arr) {
    if (arr == null || arr.length == 0) {
        return;
    }
    int n = 1 << arr.length;
    for (int i = 0; i < n; i++) {
        for (int j = 0; j < arr.length; j++) {
            if (((i >> j) & 0x1) == 1) {
                System.out.print(arr[j] + " ");
            }
        }
        System.out.println();
    }
}

二进制法是利用二进制和子集的关系。

知道了上面的问题,就可以求以下问题了:一个没有重复元素的数组,求其子集,要求子集中有m个元素。下面用位向量发去求解。

void subset(int[] arr, final int n, boolean[] path, int cur, int m) {
    if (arr == null || arr.length == 0 || path == null) {
        return;
    }
    if (cur == arr.length) {
        cnt++;
        if (m == n) {
            for (int i = 0; i < path.length; i++) {
                if (path[i]) {
                    System.out.print(arr[i] + " ");
                }
            }
            System.out.println();
        }
        return;
    }

    path[cur] = false;
    subset(arr, n, path, cur + 1, m);

    path[cur] = true;
    subset(arr, n, path, cur + 1, m + 1);
}

cnt是静态变量,为了是测试复杂度,cnt = 2^len。指数复杂度是比较大的,怎么能减小复杂度呢?答案是给解答树剪枝

void subset(int[] arr, final int n, boolean[] path, int cur, int m) {
    if (arr == null || arr.length == 0 || path == null || m > n) {
        return;
    }
    if (cur == arr.length) {
        System.out.println(Arrays.toString(path));
        cnt++;
        if (m == n) {
            for (int i = 0; i < path.length; i++) {
                if (path[i]) {
                    System.out.print(arr[i] + " ");
                }
            }
            System.out.println();
            return;
        }
    }

    if (m + (arr.length - cur) >= n) {
        if (m + (arr.length - cur - 1) >= n) {
            path[cur] = false;
            subset(arr, n, path, cur + 1, m);
        }

        if (m < n) {
            path[cur] = true;
            subset(arr, n, path, cur + 1, m + 1);
        }
    }
}

cnt = Cnm,这样就把复杂度(枚举量)从指数降低到了组合数级别。事实上,可以去掉参数m,只要走到递归边界,则有m=n。

你可能感兴趣的:(求组合数)