Leetcode之数组的排列组合问题

一、备注

       最近做leetcode的过程经常遇到有关数组排列组合的问题,leetcode的讨论中也涉及到这些,于是将其中的内容整理出来,供自己日后复习之用,这些问题都可以划分为有重复元素和无重复元素两种,基本方法都是深度优先遍历。

二、子数组

        给定数组求子集的组合。

       1.给定数组不包含重复元素的情况

        这种情况就是求给定数组的幂集(power set),代码如下。

public List<List<Integer>> subsets(int[] nums) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, 0);
    return list;
}

private void backtrack(List<List<Integer>> list , List<Integer> tempList, int [] nums, int start){
    list.add(new ArrayList<>(tempList));
    for(int i = start; i < nums.length; i++){
        tempList.add(nums[i]);
        backtrack(list, tempList, nums, i + 1);
        tempList.remove(tempList.size() - 1);
    }
}

       通过排序可以更好地避免子集重复地情况发生。backtrack函数中在向下遍历的过程中没有直接new出一个新的list,而是在加入到结果集中的时候在利用当前子数组构造新数组,这样节省了不少空间,后面的操作皆是如此。

       当然,也可以用二进制编码的方法来做,假设给定数组(无重复)有n个元素,则可能的集合数量有2^n种,可以用一个二进制数来表示当前元素的组合,0表示对应位置的元素不出现,1表示对应位置的元素的元素出现。

       例如,对于集合{0,1,2,3},0001表示子集{3},0110表示{1,2},对应代码如下:

class Solution{
    public static List<List<Integer>> subSetWithouDuplicate(int[] nums){
        List<List<Integer>> res=new ArrayList<>();
        for(int i=0;i<1<<nums.length;i++){
            List<Integer> subSet=new ArrayList<>();
            for(int j=0;j<nums.length;j++){
                if(((i>>j)&1)!=0){
                    subSet.add(nums[j]);
                    //System.out.print(nums[j]);
                }

            }
            //System.out.print("\n");
            res.add(subSet);

        }
        return res;
    }
}

       2.给定数组包含重复元素的情况

       这种情况就不好用之前的二进制数表示法来做了,因为会有重复的情况,比如对于数组{0,1,1},110和101表示的子数组是相同的,代码如下:
 

public List<List<Integer>> subsetsWithDup(int[] nums) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, 0);
    return list;
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, int start){
    list.add(new ArrayList<>(tempList));
    for(int i = start; i < nums.length; i++){
        if(i > start && nums[i] == nums[i-1]) continue; // skip duplicates
        tempList.add(nums[i]);
        backtrack(list, tempList, nums, i + 1);
        tempList.remove(tempList.size() - 1);
    }
} 

       操作同样是先对给定数组排序,然后再用dfs进行遍历。基本思想就是遇到和前一个元素相同的元素就跳过当前位置,避免重复,同时要标注当前元素的位置。

       对于i > start && nums[i] == nums[i-1],i>start说明nums[i-1]还未被加入到当前集合中,nums[i-1]==nums[i]说明当前元素与前一元素相同,这种情况下需要跳过当前元素。

三、元素的排列组合

       给定数组的情况求数组中所有元素的排列组合的情况。例如{1,2}的组合有{1,2}和{2,1}

       1.给定数组不包含重复元素的情况

        和之前的方法类似,同样利用深度优先遍历,只不过需要判断当前list的大小是否为所给定数组的长度。

   

public List<List<Integer>> permute(int[] nums) {
   List<List<Integer>> list = new ArrayList<>();
   // Arrays.sort(nums); // not necessary
   backtrack(list, new ArrayList<>(), nums);
   return list;
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums){
   if(tempList.size() == nums.length){
      list.add(new ArrayList<>(tempList));
   } else{
      for(int i = 0; i < nums.length; i++){ 
         if(tempList.contains(nums[i])) continue; // element already exists, skip
         tempList.add(nums[i]);
         backtrack(list, tempList, nums);
         tempList.remove(tempList.size() - 1);
      }
   }
} 

      此时对给定数组进行预排序就没有必要了。

      2.给定数组包含重复元素的情况

      代码如下:

      

public List<List<Integer>> permuteUnique(int[] nums) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, new boolean[nums.length]);
    return list;
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, boolean [] used){
    if(tempList.size() == nums.length){
        list.add(new ArrayList<>(tempList));
    } else{
        for(int i = 0; i < nums.length; i++){
            if(used[i] || i > 0 && nums[i] == nums[i-1] && !used[i - 1]) continue;
            used[i] = true; 
            tempList.add(nums[i]);
            backtrack(list, tempList, nums, used);
            used[i] = false; 
            tempList.remove(tempList.size() - 1);
        }
    }
}

        和前面一样,需要跳过重复的元素。

四、集合元素的和

         给定数组,求元素和为给定值的子数组

         1.元素可以重复

          比如{0,1,2,3},target=6,有几种可能,{1,1,1,1,1,1}、{2,2,2}等(未列举完全)。

          代码如下:

          

public List<List<Integer>> combinationSum(int[] nums, int target) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, target, 0);
    return list;
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, int remain, int start){
    if(remain < 0) return;
    else if(remain == 0) list.add(new ArrayList<>(tempList));
    else{ 
        for(int i = start; i < nums.length; i++){
            tempList.add(nums[i]);
            backtrack(list, tempList, nums, remain - nums[i], i); // not i + 1 because we can reuse same elements
            tempList.remove(tempList.size() - 1);
        }
    }
}

        最开始我的做法是列举出所有可能的子数组,去掉重复的子数组,虽然可以通过oj,但是时间花费比较大,后来看了leetcode中的讨论,做法如上,先将给定数组排序,然后判断子数组的和是否与给定值相同,如果相同,则加入到结果集中。

 2.元素没有重复

比如{1,2,3},target=3,则结果为{1,2}和{3}.

代码如下:

public List<List<Integer>> combinationSum2(int[] nums, int target) {
    List<List<Integer>> list = new ArrayList<>();
    Arrays.sort(nums);
    backtrack(list, new ArrayList<>(), nums, target, 0);
    return list;
    
}

private void backtrack(List<List<Integer>> list, List<Integer> tempList, int [] nums, int remain, int start){
    if(remain < 0) return;
    else if(remain == 0) list.add(new ArrayList<>(tempList));
    else{
        for(int i = start; i < nums.length; i++){
            if(i > start && nums[i] == nums[i-1]) continue; // skip duplicates
            tempList.add(nums[i]);
            backtrack(list, tempList, nums, remain - nums[i], i + 1);
            tempList.remove(tempList.size() - 1); 
        }
    }
} 

 和前面的做法类似,先对给定数组进行排序,然后跳过重复的数组。
 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值