LeetCode 全排列、子集、组合总数

1. 全排列

LeetCode 46. Permutations

题目描述

给定一个含有不同数字的集合,返回所有可能的全排列。

比如,
[1,2,3] 具有如下排列:

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

分析

全排列首先考虑深度优先搜索,每个深度的遍历都从 0nums.length - 1
每次循环开始需要判断当前值是否已经使用过,即 if (!tempList.contains(nums[i]))

创建一个List tempList存放临时数据
tempList.size() == nums.length
res.add(new ArrayList(tempList))将tempList存入结果
此处不能直接add.(tempList),否则改变tempList也会导致结果的改变

特别注意每次递归结束前都需要将刚加入的值从tempList中去掉

tempList.remove(tempList.size() - 1)

代码

class Solution {
    List> res = new ArrayList>();
    List tempList = new ArrayList();
    public List> permute(int[] nums) {
        dfs(nums);
        return res;
    }
    public void dfs(int[] nums) {
        if (tempList.size() == nums.length) {
            res.add(new ArrayList(tempList));
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            if (!tempList.contains(nums[i])) {
                tempList.add(nums[i]);      
                dfs(nums);
                tempList.remove(tempList.size() - 1);
            }
        }
    }
}

LeetCode 47. PermutationsII

题目描述

给定一个可能包含重复数字的集合,返回所有可能的不同全排列。

例如,
[1,1,2] 有以下不同全排列:

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

分析一

此题跟上一题的区别在于给定数组可能包含重复数字,所以首先将数组排序 Arrays.sort(nums)

由于数组可能包含重复数字,所以不能用if (!tempList.contains(nums[i]))判断当前值是否使用过
需要创建一个数组boolean[] used = new boolean[nums.length]记录每个位置的值是否使用过(上题也可以使用这个方法)

但还存在一个问题,由于数组包含重复数字,tempList可能一模一样
即可能出现两个[1,1,2]
两个1分别来自两个位置

所以当每一层遍历到第二次或更多次,即i > 0
需要判断前一个值(num[i - 1])是否被使用过,如果没使用过,继而判断当前值与前一个值是否相等

if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue; 

代码一

class Solution {
    List> res = new ArrayList>();
    List tempList = new ArrayList();
    public List> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        res.add(new ArrayList());
        dfs(nums, 0);
        return res;
    }
    public void dfs(int[] nums, int step) {
        if (step == nums.length) return;
        for (int i = step; i < nums.length; i++) {
            if (i > step && nums[i] == nums[i - 1]) continue;
            tempList.add(nums[i]);
            res.add(new ArrayList(tempList));
            dfs(nums, i + 1);
            tempList.remove(tempList.size() - 1);
        }
    }
}

分析二

还有一种投机取巧的方,利用List改写了equals()方法
所以不需要排序,可以直接在加入结果前判断res中是否已经包含了这个List

if (!res.contains(tempList))

List中的contains()是线性搜索,即时间复杂度为O(n),所以不推荐用这种方法

代码二

class Solution {
    List> res = new ArrayList>();
    List tempList = new ArrayList();   
    public List> permuteUnique(int[] nums) {
        boolean[] used = new boolean[nums.length];
        Arrays.sort(nums);
        dfs(nums, 0, used);
        return res;
    }
    public void dfs(int[] nums, int step, boolean[] used) {
        if (step == nums.length) {
            if (!res.contains(tempList)) {
                res.add(new ArrayList(tempList));
            }
            return;
        }
        for (int i = 0; i < nums.length; i++) {
            // if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) continue; 
            if (!used[i]) {
                used[i] = true;
                tempList.add(nums[i]);      
                dfs(nums, step + 1, used);
                used[i] = false;
                tempList.remove(tempList.size() - 1);
            }
        }
    }
}

2. 子集

LeetCode 78. Subsets

题目描述

给定一组不同的整数 nums,返回所有可能的子集(幂集)。

注意事项:该解决方案集不能包含重复的子集。

例如,如果 nums = [1,2,3],结果为以下答案:

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

分析

子集与全排列有两大区别

  1. 输出的List长度不等
    所谓子集,就是要求该集合所包含的所有集合
    所以每次循环都要将tepmList加入res
    而不是等tempList.size() == nums.length
  2. List中元素不能重复
    在全排列中,结果中每个List包含的元素都相同,只是顺序不一样
    [1,2,3]和[3,2,1]
    子集则不同,每个List中的元素都不相同,所以循环不能再从0开始
    需要重新定义一个变量start作为dfs()的输入参数
    每次递归将start设为i + 1 即不会遍历之前访问过的元素

代码

class Solution {
    List> res = new ArrayList>();
    List tempList = new ArrayList();
    public List> subsets(int[] nums) {
        Arrays.sort(nums);
        res.add(new ArrayList());
        dfs(nums, 0);
        return res;
    }
    public void dfs(int[] nums, int start) {
        if (start == nums.length) return;
        for (int i = start; i < nums.length; i++) {
            tempList.add(nums[i]);
            res.add(new ArrayList(tempList));
            dfs(nums, i + 1);
            tempList.remove(tempList.size() - 1);
        }
    }
}

 

LeetCode 90. SubsetsII

题目描述

给定一个可能包含重复整数的列表,返回所有可能的子集(幂集)。

注意事项:解决方案集不能包含重复的子集。

例如,如果 nums = [1,2,2],答案为:

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

分析

此题是4778题的结合
因为含有重复数字,所以首先需要先将给定数组排序
参考前两题,本题只需要在每次循环开始前判断当前值(nums[i])与前一个值(nums[i-1)是否相等

if (i > step && nums[i] == nums[i - 1]) continue;

由于start变量,所以每次循环都不会遍历之前访问过的元素
因此不需要判断前一个值是否使用过(因为肯定没使用过)

代码

class Solution {
    List> res = new ArrayList>();
    List tempList = new ArrayList();
    public List> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        res.add(new ArrayList());
        dfs(nums, 0);
        return res;
    }
    public void dfs(int[] nums, int step) {
        if (step == nums.length) return;
        for (int i = step; i < nums.length; i++) {
            if (i > step && nums[i] == nums[i - 1]) continue;
            tempList.add(nums[i]);
            res.add(new ArrayList(tempList));
            dfs(nums, i + 1);
            tempList.remove(tempList.size() - 1);
        }
    }
}

3. 组合总数

LeetCode 39. Combination Sum

题目描述

给一组候选数字(C)(没有重复)并给一个目标数字(T),找出 C 中所有唯一的组合使得它们的和为 T。

可以从 C 无限次数中选择相同的数字。

说明:

  • 所有数字(包括目标)都是正整数。
  • 解集合中没有相同组合。

例如,给一个候选集合 [2, 3, 6, 7] 并且目标为 7
一个解的集合为:

[
  [7],
  [2, 2, 3]
]

分析

本题与子集的区别在于数组中的数字可以无限使用
因此递归中start参数传入的值应为i而不是i + 1
本题还需定义一个remain参数来记录动态目标值

代码

class Solution {
    List> res = new ArrayList>();
    List tempList = new ArrayList();
    public List> combinationSum(int[] nums, int target) {
        dfs(nums, target, 0);
        return res;
    }
    public void dfs(int[] nums, int remain, int step) {
        if (remain < 0) return;
        else if (remain == 0) {
            res.add(new ArrayList(tempList));
            return;
        }
        for (int i = step; i < nums.length; i++) {
            tempList.add(nums[i]);      
            dfs(nums, remain - nums[i], i);
            tempList.remove(tempList.size() - 1);           
        }
    }
}

LeetCode 40. Combination SumII

题目描述

给定候选号码数组 (C) 和目标总和数 (T),找出 C 中候选号码总和为 T 的所有唯一组合。

C 中的每个数字只能在组合中使用一次。

注意:

  • 所有数字(包括目标)都是正整数。
  • 解决方案集不能包含重复的组合。

例如,给定候选集合 [10, 1, 2, 7, 6, 1, 5] 和目标总和数 8
可行的集合是:

[
  [1, 7],
  [1, 2, 5],
  [2, 6],
  [1, 1, 6]
]

分析

首先排序!!!
判断前后值!!!

if (i > step && nums[i] == nums[i - 1]) continue; 

代码

class Solution {
    List> res = new ArrayList>();
    List tempList = new ArrayList();
    public List> combinationSum2(int[] nums, int target) {
        Arrays.sort(nums);
        dfs(nums, target, 0);
        return res;
    }
    public void dfs(int[] nums, int remain, int step) {
        if (remain < 0) return;
        else if (remain == 0) {
            res.add(new ArrayList(tempList));
            return;
        }
        for (int i = step; i < nums.length; i++) {
            if (i > step && nums[i] == nums[i - 1]) continue; 
            tempList.add(nums[i]);      
            dfs(nums, remain - nums[i], i + 1);
            tempList.remove(tempList.size() - 1);                    
        }
    }
}

4. 小结

  1. 这类排列问题首先考虑深度优先搜索
  2. 如果结果的List中不能包含相同元素,需要引入变量start来标记循环起始位置,使每次递归不再遍历之前访问过的元素
  3. 如果数组含有重复数字首先排序
    然后每次循环前判断当前值(nums[i])与前一个值(nums[i - 1])是否相等
  4. 考虑res.add(new ArrayList(tempList))的时机,是在每次循环中添加还是等达到某个条件是再添加
  5. 要排除相同的List,可以利用List中的contains()方法,但这是线性搜索,时间复杂度为O(n),不适合在递归中使用,所以不推荐

你可能感兴趣的:(LeetCode)