序言
昨天在lintcode上遇到了排列组合的问题,感觉有思路,但是写起来,还是不好写,反应出写代码的能力还是太差呀,要多加练习,什么时候,代码能够跟上思路。
排列
排列问题,对于这些问题,我们大多都是可以通过朴素算法,暴力解决,这样在工程上肯定不可取的,所以我们采取更优的方法来解决这些问题,对于排列问题,排列2个和排列10个其基本上是没有区别的,也就是实现思路上是相同的,因此我们就可以通过这种递归的方式,将问题进行小大最小子结构,然后递归去解决,如何划分呢?我们可以想每一个位置,都可以是我们待选数组中数字的任何一个,所以我们要想办法,将其每一个数字,让其在每一个位置出现,然后说到的递归,我们可以固定住第一个位置,对后面的进行全排列,因此递归,直到最后只剩下一个单位,这样我们再进行,然后在将第一个位置和其后面的进行交互,再次递归下去又得到一个新的排列。最后要记得的是,当我们进行交换之后,然后进入一次递归的时候,要将其和之前的交换再交换回来。对于去重的实现,则是在交换的时候进行一个判断,如果两个值是相同的,则不再让其进行交换。但是这种方式只是在一定程度上去重,所以我们还需要自己在添加的时候做一个判断,来将重复的元素彻底去掉。
无重复
实现代码
public ArrayList<ArrayList<Integer>> permute(ArrayList<Integer> nums) {
// write your code here
ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
if(nums==null||nums.size()==0)
return result;
int len = nums.size();
int start = 0;
travel(nums,result,start,len);
return result;
}
public void travel(ArrayList<Integer> nums,ArrayList<ArrayList<Integer>> result,int start,int end){
//递归的边界返回条件
if(start==end){
result.add(nums);
}
//遍历每一个位置进行调换,调换后再恢复
for(int i=start; i<end; i++){
int tmp = nums.get(start);
nums.set(start,nums.get(i));
nums.set(i,tmp);
ArrayList<Integer> tmpList = new ArrayList<Integer>(nums);
travel(tmpList,result,start+1,end);
tmp = nums.get(start);
nums.set(start,nums.get(i));
nums.set(i,tmp);
}
}
有重复
public class Solution {
public ArrayList<ArrayList<Integer>> permuteUnique(ArrayList<Integer> nums) {
// write your code here
ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
if(nums==null||nums.size()==0)
return result;
int start = 0;
int len = nums.size();
travel(result,nums,start,len);
return result;
}
public void travel(ArrayList<ArrayList<Integer>> result,ArrayList<Integer> list,int start,int end){
if(start==end){
if(!result.contains(list))
result.add(list);
}else{
for(int i=start; i<end; i++){
if(list.get(i)==list.get(start)&&i!=start)
continue;
else{
int tmp = list.get(start);
list.set(start,list.get(i));
list.set(i,tmp);
ArrayList<Integer> tmpList = new ArrayList<Integer>(list);
travel(result,tmpList,start+1,end);
tmp = list.get(start);
list.set(start,list.get(i));
list.set(i,tmp);
}
}
}
}
}
组合
写之前,想的组合和排列是不会差太多,写起来,感觉还是蛮费力的,代码表述思维能力还是不够强呀,同时好像并没有思维,没有一个具体的思路。首先是从给定的区间中,先确定组合的第一个数字,这个数字可以是0-n的所有数字,然后再递归确定第二个,因为是一个递归的过程所以这两步可以假设已经确定了所有的位置了,当组合到最后一个位置的时候,将其该位置的数移除掉,然后进行下一次的遍历,递归。从而保证所有可能的组合出现。
实现代码
public class Solution {
public List<List<Integer>> combine(int n, int k) {
// write your code here
if(n==0||k==0) return null;
List<List<Integer>> res = new ArrayList<>();
ArrayList<Integer> cur = new ArrayList<>();
getcombineList(n, k, 0, res, cur,1);
return res;
}
public void getcombineList(int n,int k,int x,List<List<Integer>> res,ArrayList<Integer> cur,int start ){
if(x==k){
res.add(new ArrayList<Integer>(cur));
return;
}
for(int i = start;i<=n;i++){
cur.add(i);
getcombineList(n, k, x+1, res, cur,i+1);
cur.remove(cur.size()-1);
}
}
}
后记
再次回顾整理下组合和排列的思路,对于排列,是通过将当前位置和向后的所有位置划分为两个结构,然后不断地递归解决,将当前位置不断地和其后面的进行交换,交换后递归到下一层,同时再恢复原来的位置,然后再次递归。对于含有重复数字的,我们要将其添加到结果集的时候,进行一个判断,然后将其剔除掉。对于组合的问题,是将确定的区间中的数不断的移除的过程,同时引进新的数,然后保持整体的不变,所以递归的时候,从最后一个数开始递归完成删除掉,删除完成,最后一个位置上,在最后位置之后的所有数都已经出现被添加进来过,然后回到倒数第二个位置,这个时候,删除掉倒数第二位置,然后找到一个不含有之前倒数第二个位置的组合。依次递归直到第一个位置。简言之,从所有的数中,选出第一个位置,然后递归下去每一个都找到其当前位置的数,然后删除当前的数,选择下一个数。
在我们解决一些问题的时候很多情况下,可能需要我们先通过对问题中的数据进行全排列或者是找出其组合来进行解决。后期有问题再添加上来。
每日Lintcode一击,让手里的代码跟的上你的思维。
Github:https://github.com/Jensenczx/CodeEveryday