Leetcode刷题系列(一)Subsets 与 Permutation

  Subsets与Permutation这两种题型如果用Recursion的方法来解决,其思路是及其相似的。

1.Subsets I

内容

Given a set of distinct integers, S, return all possible subsets.

Note:
Elements in a subset must be in non-descending order.The solution set must not contain duplicate subsets.
For example,If S = [1,2,3], a solution is:

[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

思路

  这道题是求一个集合的所有子集,对于集合中的每个元素有两种选择,放进集合中和不放进集合中。可以用树型结构表示该过程。

  另外,由于递归无法有返回值,每个子集(ArrayList<Integer>)在递归过程中可以作为参数加到最后的结果(ArrayList<ArrayList<Integer>>)中。这种情况在使用Java语言的递归中尤其常见。

代码

public class Solution {
    public ArrayList<ArrayList<Integer>> subsets(int[] num) {
        ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
        if(num == null || num.length == 0) {
            return result;
        }
        ArrayList<Integer> list = new ArrayList<Integer>();
        Arrays.sort(num);  
        subsetsHelper(result, list, num, 0);
        return result;
    }

    private void subsetsHelper(ArrayList<ArrayList<Integer>> result,
        ArrayList<Integer> list, int[] num, int pos) {
        result.add(new ArrayList<Integer>(list));
        for (int i = pos; i < num.length; i++) {
            list.add(num[i]);
            subsetsHelper(result, list, num, i + 1);
            list.remove(list.size() - 1);
        }
    }
}

重点  

  1. 前面的边界检查,如果不存在则返回空集。

  2. subsetsHelper则为递归程序,子集被当成参数传递使得递归过程中数据不会丢失,还可以在类中增加私有成员进行储存。
  3. for循环中的add和remove方法是关键,代表放进和不放进两种选择,这两个方法的位置可以看成是树形结构的向下走和往回走。

2.Subsets II

内容

Given a collection of integers that might contain duplicates, nums, return all possible subsets.

Note:
Elements in a subset must be in non-descending order.The solution set must not contain duplicate subsets.
For example,If nums = [1,2,2], a solution is:

[
  [2],
  [1],
  [1,2,2],
  [2,2],
  [1,2],
  []
]

思路

  Subsets IISubsets I 的区别仅在于集合中存在重复元素。因此,如果使用 Subsets I 的方法就会导致最终的结果集中存在一些重复的子集(注意:集合中的元素之间是没有次序关系的)。

  此时在 Subsets I 的解题思路基础上,加一些特殊处理即可。以题干中的例子为例,将集合中的重复元素2特殊处理,在每一次的递归过程中要么取第一个2放进集合,要么所有的2都不选进集合中。

代码

  此处仅贴出与第一部分不同,即进行特殊处理的代码。

private void subsetsHelper(ArrayList<ArrayList<Integer>> result,
    ...
    for (int i = pos; i < num.length; i++) {
        if ( i != pos && num[i] == num[i - 1]) {
            continue;
        }    
        ...
    }
}

重点

  由于每次递归调用函数时,pos的值会变化,因此是可以把所有重复的2选进集合的,确保了存在2的子集的单一性和正确性。


3.Permutation I

内容

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], and [3,2,1].

思路

  此题解题思路与上题很像,区别在与此题没有位置关系。换种说法,此题可以解读为给出几个元素,生成这些元素的排列组合,因此元素之间是没有位置关系的。

代码

public class Solution {
    public ArrayList<ArrayList<Integer>> permute(int[] num) {
         ArrayList<ArrayList<Integer>> rst = new ArrayList<ArrayList<Integer>>();
         if (num == null || num.length == 0) {
             return rst; 
         }

         ArrayList<Integer> list = new ArrayList<Integer>();
         helper(rst, list, num);
         return rst;
    }

    public void helper(ArrayList<ArrayList<Integer>> rst, ArrayList<Integer> list, int[] num){
        if(list.size() == num.length) {
            rst.add(new ArrayList<Integer>(list));
            return;
        }

        for(int i = 0; i<num.length; i++){
            if(list.contains(num[i])){
                continue;
            }
            list.add(num[i]);
            helper(rst, list, num);
            list.remove(list.size() - 1);
        }

    }
}

重点

 此代码在思路上与 Subsets 相似易于理解且在可以套用在Permutation II上。


4.Permutation II

内容

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], and [2,1,1].

思路

  主要特殊解决重复问题,与 Subsets II 极为相似。

代码

 代码与 Permutation I 一样,主要是下面这段代码解决了重复的问题。

public void helper(ArrayList<ArrayList<Integer>> rst, ArrayList<Integer> list, int[] num){
        ...
        for(int i = 0; i<num.length; i++){
            if(list.contains(num[i])){
                continue;
            }
            ...
        }
    }
}

5.总结

  1. 任何递归程序均可写成非递归的形式,一般可以有两种方式改写:使用编码方式、使用栈(最常用)。如果一个程序使用非递归形式易懂,那么应该使用非递归形式,因为递归形式毕竟有调用程序的开销。这里给出编码方式解决 Subsets I 的代码。

    class Solution {
        public ArrayList<ArrayList<Integer>> subsets(int[] nums) {
            ArrayList<ArrayList<Integer>> result = new ArrayList<ArrayList<Integer>>();
            int n = nums.length;
            Arrays.sort(nums);
    
            // 1 << n is 2^n
            // each subset equals to an binary integer between 0 .. 2^n - 1
            // 0 -> 000 -> []
            // 1 -> 001 -> [1]
            // 2 -> 010 -> [2]
            // ..
            // 7 -> 111 -> [1,2,3]
            for (int i = 0; i < (1 << n); i++) {
                ArrayList<Integer> subset = new ArrayList<Integer>();
                for (int j = 0; j < n; j++) {
                    // check whether the jth digit in i's binary representation is 1
                    if ((i & (1 << j)) != 0) {
                        subset.add(nums[j]);
                    }
                }
                result.add(subset);
            }       
            return result;
        }
    }
    
  2. 相似题型(后续补充)


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值