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

来源:互联网 发布:linux sendmail smtp 编辑:程序博客网 时间:2024/05/22 06:29

组合数

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++;            }        }    }
0 0