剑指 Offer II 079+080+081+082

所有子集

给定一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。

分析:

递归实现。定义函数 dfs ( cur , n ) \text{dfs}(\textit{cur}, n) dfs(cur,n) 参数表示当前位置是 cur \textit{cur} cur,原序列总长度为 n。原序列的每个位置在答案序列中的状态有被选中和不被选中两种,用 t 数组存放已经被选出的数字。在进入 dfs ( cur , n ) \text{dfs}(\textit{cur}, n) dfs(cur,n) 之前 [ 0 , cur − 1 ] [0, \textit{cur} - 1] [0,cur1]位置的状态是确定的,而 [ cur , n − 1 ] [\textit{cur}, n - 1] [cur,n1] 内位置的状态是不确定的, dfs ( cur , n ) \text{dfs}(\textit{cur}, n) dfs(cur,n) 需要确定 cur 位置的状态,然后求解子问题 dfs ( c u r + 1 , n ) {\text{dfs}(cur + 1}, n) dfs(cur+1,n)。对于 cur 位置,我们需要考虑a[cur] 取或者不取,如果取,我们需要把 a[cur] 放入一个临时的答案数组中(即上面的 t),再执行 dfs ( c u r + 1 , n ) {\text{dfs}(cur + 1}, n) dfs(cur+1,n),执行结束后需要对 t 进行回溯;如果不取,则直接执行 dfs(cur+1,n)。在整个递归调用的过程中,cur 是从小到大递增的,当 cur 增加到 n 的时候,记录答案并终止递归。

class Solution {
    List<Integer> list = new ArrayList<>();
    List<List<Integer>> res = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        dfs(0, nums);
        return res;

    }
    public void dfs(int cur, int[] nums){
        if (cur == nums.length){
            res.add(new ArrayList<>(list));
            return;
        }
        list.add(nums[cur]);
        dfs(cur+1, nums);
        list.remove(list.size()-1);
        dfs(cur+1, nums);
    }
}

含有K个元素的组合

给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。

分析:

与上一题类似,使用递归来解决。将上一个题的函数dfs中增加一个参数k,即 dfs ( cur , n , k ) \text{dfs}(\textit{cur}, n, k) dfs(cur,n,k),如果当前数组的长度==k则把数组添加到答案中去,同时增加剪枝代码:如果当前数组长度+剩余长度(n-cur+1) < k的话直接舍弃掉下面的递归。

class Solution {
    List<Integer> list = new ArrayList<>();
    List<List<Integer>> ans = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        if (k>n) return null;
        dfs(1,n,k);
        return ans;

    }
    public void dfs(int cur, int n, int k){
        if (list.size() == k){
            ans.add(new ArrayList<>(list));
            return;
        }
        if (list.size()+(n-cur+1)<k) return;
        list.add(cur);
        dfs(cur+1, n ,k);
        list.remove(list.size()-1);
        dfs(cur+1,n,k);
    }
}

允许重复选择元素的组合

给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。

candidates 中的数字可以无限制重复被选取。如果至少一个所选数字数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的唯一组合数少于 150 个。

分析:

dfs+剪枝+回溯。首先顺序的选择candidates的数字,对于当前的数字有两种选择:1. 选, 下次依旧针对这个数选或不选;2. 不选, 直接跳过该数字,后面不选这个数。

class Solution {
    List<List<Integer>> result = new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        dfs(candidates, target, 0, new ArrayList<Integer>());
        return result;
    }

    public void dfs(int[] candidates, int target, int start, ArrayList<Integer> list) {
        if (target == 0) {
            result.add(new ArrayList(list));
            return;
        }
        if (target < 0 || start >= candidates.length) return;

        //对于当前数字candidates[start]
        //选
        list.add(candidates[start]);
        dfs(candidates, target-candidates[start], start, list);
        list.remove(list.size()-1);
        //或者不选
        dfs(candidates, target, start+1, list);
    }
}

含有重复元素集合的组合

给定一个可能有重复数字的整数数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用一次,解集不能包含重复的组合。

分析:

定义函数dfs(idx,target):选中candidate[idx],同时与从下标为[idx + 1, candidate.length)中选取元素一起构成和为target的所有不重复组合的集合。为了避免重复的答案,首先要做的就是给数组排序,如果说在同一级递归中,遇到两个相同的数,应该只dfs靠前的那一个一次。原因可以这样理解,如果现在遇到下标位idx,idx + 1的两个数是相同的,那么对于集合dfs(idx, target) 和 dfs(idx + 1, target),后者就是前者的一个子集,所以在同一级递归中,对于相同的数,只应该dfs一次,并且是下标最小的那一个。
剪枝就是基于很直接的思想,例如:前面已经给数组排序了,如果递归的过程中当前值比target大,那么说明后面的值不可能再找出一组元素和为target,所以此时就可以立即结束递归返回。

class Solution {
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        int n = candidates.length;
        List<List<Integer>> ans = new ArrayList<>();
        Arrays.sort(candidates);
        dfs(candidates, n, 0, target, new ArrayList<>(), ans);
        return  ans;
    }

    public void dfs(int[] candidate, int n, int idx, int target, List<Integer> list, List<List<Integer>> ans) {
        if (target == 0) {
            ans.add(new ArrayList<>(list));
            return ;
        }
        for (int i = idx; i < n; i++) {
            if (candidate[i] > target) { // 剪枝
                break;
            }
            if (i > idx && candidate[i] == candidate[i - 1]) { // 剪枝、避免重复
                // 因为前面那个同样的数已经经历过dfs,再拿同样的数dfs就会得到重复的答案
                continue;
            }
            list.add(candidate[i]);
            dfs(candidate, n, i + 1, target - candidate[i], list, ans);
            list.remove(list.size() - 1);
        }
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值