备忘 - 组合与排列的算法实现

排列

我的处理方式主要是通过递归方式进行遍历。同时利用boolean[]数组,来判断某个数字有没有被用过。辅助以Stack,进行数据的存储,注意Stack可以是正常的Stack类,也可以是一般的数组。算法复杂度O(P(n, k))。

public void iteratePermutation(int n, int k) {// P(n, k)
    boolean[] used = new boolean[n];
    int[] permutation = new int[k];
    for (int i = 0 ; i < n; i++) {
        choose(i, k , used, permutation);
    }
}
private void choose(int i , int k ,boolean[] used,  int[] permutation) {
    used[i] = true;
    permutation[permutation.length - k] = (i+1);
    k--;
    if (k == 0) {
        doSomething(permutation);
        used[i] = false;
        return;
    }
    for (int j = 0 ; j < used.length; j++) {// 排列与组合的最大区别是,排列可以用到以前的数字,而组合不能。
        if (!used[j]) {
            choose(j, k, used, permutation);
        }
    }
    used[i] = false;// 这一步容易忘记
}

private void doSomething(int[] permutation) {
    StringBuilder sb = new StringBuilder();
    for (int i : permutation) {
        sb.append(i).append(' ');
    }
    System.out.println(sb);
}

组合

组合的话,只要在排列的基础上稍加改动就行,不要用到下标 i 及以前的元素。int j = i+1。可以对这个方法进行剪枝操作,如果剩下的元素不足以凑够k个,那么直接返回。否则,复杂度就会变成O(Combination(n, k)+ Combination(n, k-1) + ... + Combination(n, 1))。

    for (int j = i+1 ; j < used.length; j++) {//组合从i+1开始遍历。

组合的另外一种实现方式:

利用递归的方式,每个元素可取可不取。对于Combination(n, k)来说,只要拿到了k个元素就可以了。

这种方法必须进行剪枝操作,就是当剩余元素不足k个时,终止递归。如果不进行剪枝,复杂度会上升到O(2^n)。

class Solution {
    Stack<Integer> stack = new Stack<>();
    int n;
    public void iterateCombination(int n, int k) {
        this.n  = n;
        visit(0, k);
    }
    private void visit(int cur, int k) {
        if ((n - cur) < k) {// 剪枝
            return;
        }
        if (k == 0) {
            System.out.println(stack);
            return;
        }
        // if (cur == n) return;
        stack.push(cur+1);// 有这个元素
        visit(cur+1, k-1);
        stack.pop();
        visit(cur+1, k);// 没有这个元素
    }
}

 

数组的子集

在一个大小为n的数组中,任意一个元素可以存在或者不存在,所以子集中一共有2^n个元素。那么如何获取呢?

利用掩码

思路是,得到0~2^n-1个数,int i = 0 ; i < (1<<n);i++, 进行遍历。将i视作一个二进制掩码,如果掩码为1,那么获取这个值,否则抛弃这个值。

public void iterateSubset(int[] nums) {
    for (int i = 0 ; i < (1<<nums.length); i++) {
        int mask = i;
        List<Integer> element = new LinkedList<>();
        for (int j = 0 ; j < nums.length; j++) {
            if ((mask & 1) == 1) { // 掩码为1,添加这个值
                element.add(nums[j]);
            }
            mask = mask >> 1;
        }
        doSomething(element);
    }
}

利用递归

元素可以在,也可以不在,这一点可以在递归中实现。这种输出顺序好像会比上面的好些,看你是否有访问顺序的需要了。

class Solution {
    Stack<Integer> stack = new Stack<>();
    public void iterateSubset(int[] nums) {
        visit(0, nums);
    }
    private void visit(int cur, int[] nums) {
        if (cur == nums.length) {
            System.out.println(stack);
            return;
        }
        stack.push(nums[cur]);// 有这个元素
        visit(cur+1, nums);
        stack.pop();
        visit(cur+1, nums);// 没有这个元素
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值