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

被折叠的 条评论
为什么被折叠?



