Leetcode:Combinations组合数&&Permutations排列数

本文详细介绍了组合数和排列数的算法实现,包括递归、深度优先搜索(DFS)及回溯等方法,并针对组合数之和、组合数之和2等问题提供了多种解决方案。

组合数

Given two integers n and k, return all possible combinations of k numbers out of 1 … n.

For example,If n = 4 and k = 2, a solution is:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]

解析:首先我的想法是k个轮次,每一轮给每个组合加一个数。i从1到k遍历,第一次就是[[1],[2],[3]],为了减少次数,剪去n-last小于还需加入的个数的情况,(last表示每个组合中最后面即最大的数)去掉加入即使加入当前值,后果的个数也不够的了情况。

    public static List<List<Integer>> mycombine(int n, int k) {

        List<List<Integer>> combineList = new ArrayList<List<Integer>>();
        for (int i = 1; i <= n; i++) {
            List<Integer> tmp = new ArrayList<>();
            tmp.add(i);
            combineList.add(tmp);
        }

        int length = combineList.size();

        if (k == 1)
            return combineList;
        else
            for (int i = 1 ; i < k ;i++)
                combineList.remove(--length);                   //根据k的大小,删除倒数k-1个元素

        int curlength = 1;
        while (--k > 0) {
            for (int i = 0; i < combineList.size(); i++) {
                List<Integer> tmp = combineList.get(i);
                if (tmp.size() == curlength) {                                   //因为不断向List里面添加先组合,加入的是已经在当前轮插入过的,就不需要再插入了
                    int last = tmp.get(curlength - 1);
                    if (last < n && (n-last)>=k) {                              //相当于减枝,后面不够了
                        last++;
                        tmp.add(last);
                        combineList.set(i, tmp);
                    }
                    while (last < n && (n-last)>=k) {                                           //如果最后一个元素仍然小于n,那么还可以换一个大的继续插入。 这时候要先拷贝出来,再删除最后一个刚插入的元素,然后插入新的
                        last++;
                        List<Integer> tmp1 = new ArrayList<>();
                        tmp1.addAll(combineList.get(i));
                        tmp1.remove(curlength);
                        tmp1.add(last);
                        combineList.add(tmp1);
                    }
                }

            }
            curlength++;

        }

        return combineList;
    }

用循环次数比较恐怖,所以当我们看到这题 首先的第一想法应该是用递归。

    //LinkedList 和 ArrayList都实现了 List接口,  LinkedList更适用于插入

    //递归
    //Basically, this solution follows the idea of the mathematical formula C(n,k)=C(n-1,k-1)+C(n-1,k).

    //Here C(n,k) is divided into two situations. Situation one, number n is selected, so we only need to select k-1 from n-1 next. Situation two, number n is not selected, and the rest job is selecting k from n-1.

    //C(n,k)表示从n个数中取k个,取n的情况:C(n-1,k-1),不取n:C(n-1,k),所以 C(n,k)=C(n-1,k-1)+C(n-1,k)
    public static List<List<Integer>> combine(int n, int k) {

        if (k == n || k == 0)
        {
            List<Integer> row = new LinkedList<>();
            for (int i = 1 ; i <=k ; i++)
                row.add(i);

            return new LinkedList<>(Arrays.asList(row));

        }

        List<List<Integer>> result = combine(n-1,k-1);
        result.forEach(e -> e.add(n));
        result.addAll(combine(n-1,k));
        return result;


    }

DFS and Backtracking:这个递归是一层一层深入下去的,首先看以1开头的组合数,之后是2开头,以此对n个数做了n次DFS。复杂度为O(n!),第一次是对n个数DFS ,第二次是对n-1个数DFS…… 另外再加剪枝操作。

//DFS 和 Backtracking
    //C++ 可以通过值传递curr和引用传递res, 达到DFS的目的
    //但是 java对于List的传递 都是引用,故我们采用在最终add到res里是,使用new ArrayList<Integer>(curr) 来根据curr创建一个新的list变量
    public static List<List<Integer>> DFScombine(int n,int k)
    {
        List<List<Integer>> res = new ArrayList<>();
        if (n<=0)
            return res;
        List<Integer> curr = new ArrayList<>();

        DFS(res,curr,n,k,1);
        return res;
    }

    private static void DFS(List<List<Integer>> res, List<Integer> curr, int n, int k, int level) {

        if (curr.size() == k)
        {
            res.add(new ArrayList<Integer>(curr));
            return;
        }
        if (curr.size() > k)
            return;

        for (int i = level; i <= n ; i++)
        {
            curr.add(i);
            DFS(res,curr,n,k,i+1);
            curr.remove(curr.size()-1);
            //回溯
        }
    }

组合数之和:
Given a set of candidate numbers (C) (without duplicates) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.
The same repeated number may be chosen from C unlimited number of times.
Note:
All numbers (including target) will be positive integers.
The solution set must not contain duplicate combinations.

For example, given candidate set [2, 3, 6, 7] and target 7, 
A solution set is:
[
  [7],
  [2, 2, 3]
]

这和上面那题差不多,不同的是可以重复的选一个元素,关系到递归的参数

   public List<List<Integer>> combinationSum(int[] candidates, int target) {

       List<List<Integer>> result = new ArrayList<>();
       if (target < 0)
           return result;
       List<Integer> curr = new ArrayList<>();
       Arrays.sort(candidates);                                    
       //排序,在这里不排序好像也没差
       DFS(result, curr, candidates, target, 0);
       return result;
   }

private void DFS(List<List<Integer>> result, List<Integer> curr, int[] candidates, int target, int start) {

    if (target == 0) {
        result.add(new ArrayList<>(curr));
        return;
    } else if (target < 0)
        return;
    else
        for (int i = start; i < candidates.length; i++) {
            curr.add(candidates[i]);
            DFS(result, curr, candidates, target - candidates[i], i);             //因为可以重复
            curr.remove(curr.size() - 1);
    }
 }

组合数之和2
Given a collection of candidate numbers (C) and a target number (T), find all unique combinations in C where the candidate numbers sums to T.
Each number in C may only be used once in the combination.
给出的数组中含有重复的元素,但是我们的解不能有重复

//可以利用Set集合的特性
    //组合数2和组合数1比:区别在于组合不能重复,dfs能遍历出所有的情况,会存在重复的。   所以我利用了Set的特性先把组合数存下来,再转类型
    public List<List<Integer>> mycombinationSum2(int[] candidates, int target) {

        Set<List<Integer>> result = new HashSet<>();
        List<List<Integer>> lists = new ArrayList<>();
        if (target < 0)
            return lists;
        Arrays.sort(candidates);
        List<Integer> curr = new ArrayList<>();
        myDFS(result,curr,candidates,target,0);
        lists.addAll(result);
        return lists;

    }

    private void myDFS(Set<List<Integer>> result, List<Integer> curr, int[] candidates, int target, int start) {

        if (target == 0)
        {
            result.add(new ArrayList<>(curr));
            return;

        }else if (target<0)
            return;
        else
            for (int i = start ; i <candidates.length ; i ++)
            {
                curr.add(candidates[i]);
                myDFS(result,curr,candidates,target - candidates[i],i+1);
                curr.remove(curr.size() - 1);
            }
    }
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {

        List<List<Integer>> result = new ArrayList<>();
        if (target < 0 )
            return result;
        List<Integer> curr = new ArrayList<>();
        Arrays.sort(candidates);
        DFS(result,curr,candidates,target,0);
        return result;
    }
    private void DFS(List<List<Integer>> result, List<Integer> curr, int[] candidates, int target, int start) {
        if (target == 0 )
        {
            result.add(new ArrayList<>(curr));
            return;
        }else if (target < 0)
            return;
        else
            for (int i = start ; i < candidates.length ; i++)
            {
                if (i > start && candidates[i] == candidates[i-1])
                    continue;                                                       
                                    //可以避免重复
                curr.add(candidates[i]);
                DFS(result,curr,candidates,target-candidates[i],i+1);
                curr.remove(curr.size() - 1);                                
                                    //回溯
            }

    }

    }

/*画个图好好理解这层循环的意思,这层循环就是遍历第i层的所有情况 由start到end
  假设当我们递归到n层(B节点)时达到了条件,此时回溯到n-1层(A节点),而在第n层被(remove)出去的那个点就是candi[i],此时i++,即下一个元素如何candi[i]相等,那么就相当于A节点的下一个儿子节点和上一个大小一样,那就会导致路径相同
  例子比如 递归到[3,2] 然后回溯去掉了2,变成[3],此时下一个元素又是2,那么加进去就是重复的[3,2]*/

排列数

Given a collection of distinct numbers, return all possible permutations.

For example,
[1,2,3] have the following permutations:
[
  [1,2,3],
  [1,3,2],
  [2,1,3],
  [2,3,1],
  [3,1,2],
  [3,2,1]
]

解题思路:这个问题和组合数一样,首先我们想到的是用DFS递归求解,对数组中的每个元素,找到以它为首节点的排列。唯一的不同在于,这里需要另外一个数组来表示元素的访问与否,访问时,标为true,访问结束时,标回false。

    public static List<List<Integer>> permute(int[] nums) {

        List<List<Integer>> result = new ArrayList<>();

        Arrays.sort(nums);
        List<Integer> curr = new ArrayList<>();                 
        //Set是无序的,remove时给index,不一定会删除哪个
        DFS(result,curr,nums);

        return result;
    }

    private static void DFS(List<List<Integer>> result, List<Integer> curr, int[] nums) {

        if (curr.size() == nums.length)
        {
            result.add(new ArrayList<>(curr));
            return;
        }

        for (int i = 0 ; i < nums.length ; i ++)
        {
            if (curr.contains(nums[i])) continue;               
            //跳过已经含有的
            curr.add(nums[i]);
            DFS(result,curr,nums);
            curr.remove(curr.size() - 1);
        }

    }

Given a collection of numbers that might contain duplicates, return all possible unique permutations.

For example,
[1,1,2] have the following unique permutations:
[
  [1,1,2],
  [1,2,1],
  [2,1,1]
]

解题思路:与上题有两点不同:1.数组中有重复的元素。2.答案中不能有重复的排列
这就需要我们过滤,相同的排列了。可以先对数组排序,当我们求出以某个元素为首的排列时,后面和这个元素相同的元素都可以pass掉了。这在组合数中已经屡试不爽了。(remove出去的节点和接下来add进去的节点是同一层的。)

    public static List<List<Integer>> permuteUnique(int[] nums) {

//        Set<List<Integer>> result = new HashSet<>();             
//          使用Set 可以去重
        List<List<Integer>> result = new ArrayList<>();
        if (nums.length == 0)
            return result;

        Arrays.sort(nums);
        //这里必须先排序,不然无法通过 while(i+1<nums.length && nums[i] == nums[i+1]) i++; 来去重
        //若使用Set 则可以不需要排序
        List<Integer> curr = new ArrayList<>();
        boolean[] flag = new boolean[nums.length];              
        //默认为false

        DFS(result,curr,nums,flag);
        return result;
    }

    private static void DFS(List<List<Integer>> result, List<Integer> curr, int[] nums, boolean[] flag) {

        if (curr.size() == nums.length)
        {
            result.add(new ArrayList<>(curr));
            return;
        }

        for (int i = 0 ; i < nums.length ; i ++)
        {

            if (flag[i] == false)
            {
                flag[i] = true;
                curr.add(nums[i]);
                DFS(result,curr,nums,flag);
                curr.remove(curr.size() - 1);
                flag[i] = false;


                while(i+1<nums.length && nums[i] == nums[i+1]) i++;
            }

        }

    }
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值