dp和回溯

本文深入探讨了回溯算法在解决子集、排列、组合等问题中的应用技巧,通过实例讲解了如何根据不同问题特点选用合适的回溯框架。

参考:
https://labuladong.gitee.io/algo/1/4/

回溯:

总结几个核心:
针对子集组合问题(组合就是子集的条件中变化,相当于子集的子集):可重复选 就用i 不可重复选就用i+1
针对排列问题:不可以构造下标index!!!
针对元素重复问题:元素重复的话一定要使用排序,visited数组,然后剪枝

if(visited[i]) continue;

,特殊情况下还要多一个保证相同元素之间次序保持不变的操作

i > 0 && nums[i] == nums[i-1] && !visited[i-1]

子集问题
看元素是否重复,能否重复选!
元素不重复(可重复选), 那么使用index+1下标即可。
元素重复(可重复选),那么就要先排序然后引进visited数组,剪枝!还需要进行同一层树的标记(此前数组需要排好序),使得同一层相同元素剪枝掉

if(i > 0 && nums[i] == nums[i-1] && !visited[i-1]){
 //保证相同元素在排列中的相对位置保持不变。!visited[i-1] 也就是前一个必须没有用过!这样就可以跳过
// 1 2 2` 和 1 2` 2 其实是一样的 但是如果 2` 没有用过,那么 1 2` 2就应该被剪枝掉
                continue;
            }

全排列问题(本身就默认不允许重复选择,所以只剩下元素重复问题)
一般都是要使用**visited数组进行标记是否使用过,并且不需要index下标。**然后再回溯遍历魔板中进行判断剪枝!
元素不重复情况下

if(visited[i]) continue;

遇到可重复元素的话,还需要进行同一层树的标记(此前数组需要排好序),使得同一层相同元素剪枝掉

if(i > 0 && nums[i] == nums[i-1] && !visited[i-1]){
//保证相同元素在排列中的相对位置保持不变。!visited[i-1] 也就是前一个必须没有用过!这样就可以跳过
// 1 2 2` 和 1 2` 2 其实是一样的 但是如果 2` 没有用过,那么 1 2` 2就应该被剪枝掉
                continue;
            }
  1. 子集(中等)
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。

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

 

示例 1:

输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    public List<List<Integer>> subsets(int[] nums) {
        if(nums.length == 0) return res;
        backtracking(nums,0);
        return res;
    }
    public void backtracking(int[]nums,int index){
        res.add(new ArrayList<>(path));
        for(int i = index; i < nums.length; i++){
            path.add(nums[i]);
            backtracking(nums,i+1);
            path.remove(path.size() - 1);
        }
    }
}
  1. 子集 II(中等)
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。

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

 

示例 1:

输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]

class Solution {

    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>(); 
    boolean[]visited;
    public List<List<Integer>> subsetsWithDup(int[] nums) {
        Arrays.sort(nums);
        visited = new boolean[nums.length];
        backstracking(nums,0);
        return res;
    }
    public void backstracking(int[]nums,int index){
        res.add(new ArrayList<>(path));
        for(int i = index; i < nums.length; i++){
            if(i > 0 && nums[i] == nums[i-1] && !visited[i-1]){
                // 去重
                continue;
            }
            visited[i] = true;
            path.add(nums[i]);
            backstracking(nums,i+1);
            path.remove(path.size() - 1);
            visited[i] = false;
        }
    }
}
  1. 组合(中等)
给定两个整数 n 和 k,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

 

示例 1:

输入:n = 4, k = 2
输出:
[
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
]
示例 2:

输入:n = 1, k = 1
输出:[[1]]

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    public List<List<Integer>> combine(int n, int k) {
        backstracking(n,k,1);
        return res;

    }
    public void backstracking(int n,int k,int val){
        if(path.size() == k){
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = val; i <= n; i++){
            path.add(i);
            backstracking(n,k,i+1);
            path.remove(path.size()-1);
        }
    }
}
  1. 组合总和(中等)
给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

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

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

 

示例 1:

输入:candidates = [2,3,6,7], target = 7
输出:[[2,2,3],[7]]
解释:
23 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
7 也是一个候选, 7 = 7 。
仅有这两种组合。

class Solution {
    List<List<Integer>> res = new ArrayList();
    List<Integer> path =new ArrayList<>();
    public List<List<Integer>> combinationSum(int[] candidates, int target) {
        backstracking(candidates,target,0);
        return res;
    }
    public void backstracking(int[]candidates,int target,int index){
        if(target == 0 ){
            res.add(new ArrayList<>(path));
            return;
        }
        if(target < 0){
            return;
        }
        for(int i = index; i < candidates.length; i++){
            path.add(candidates[i]);
            backstracking(candidates,target-candidates[i],i);
            path.remove(path.size() - 1);
        }
    }
}
  1. 组合总和 II(中等)
给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

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

注意:解集不能包含重复的组合。 

 

示例 1:

输入: candidates = [10,1,2,7,6,1,5], target = 8,
输出:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    boolean[]visited;
    public List<List<Integer>> combinationSum2(int[] candidates, int target) {
        Arrays.sort(candidates);
        visited = new boolean[candidates.length];
        backstracking(candidates,target,0);
        return res;

    }
    public void backstracking(int[]candidates,int target,int index){
        if(target == 0){
            res.add(new ArrayList<>(path));
            return;
        }
        if(target < 0){
            return;
        }
        for(int i = index; i < candidates.length; i++){
            if( i >0 && candidates[i] == candidates[i-1] && !visited[i-1]){
                continue;
            }
            path.add(candidates[i]);
            visited[i] = true;
            backstracking(candidates,target-candidates[i],i+1);
            path.remove(path.size()-1);
            visited[i] = false;
        }
    }
}
  1. 组合总和 III(中等)
找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:

只使用数字19
每个数字 最多使用一次 
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

 

示例 1:

输入: k = 3, n = 7
输出: [[1,2,4]]
解释:
1 + 2 + 4 = 7
没有其他符合的组合了。

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer> path = new ArrayList<>();
    public List<List<Integer>> combinationSum3(int k, int n) {
        backstracking(k,n,1);
        return res;
    }
    public void backstracking(int k,int n,int val){
        if(k == path.size() && n == 0){
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = val; i <= 9; i++){
            path.add(i);
            backstracking(k,n-i,i+1);
            path.remove(path.size() -1);
        }
    }
}
  1. 全排列(中等)
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。

 

示例 1:

输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:

输入:nums = [0,1]
输出:[[0,1],[1,0]]

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer>path = new ArrayList<>();
    boolean[]visited;
    public List<List<Integer>> permute(int[] nums) {
        visited = new boolean[nums.length];
        backstracking(nums);
        return res;
    }
    public void backstracking(int[]nums){
        if(path.size() == nums.length){
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = 0; i < nums.length; i++){
            if(visited[i]){
                continue;
            }
            visited[i] = true;
            path.add(nums[i]);
            backstracking(nums);
            path.remove(path.size() - 1);
            visited[i] = false;
        }
    }
}
  1. 全排列 II(中等)
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。

 

示例 1:

输入:nums = [1,1,2]
输出:
[[1,1,2],
 [1,2,1],
 [2,1,1]]

class Solution {
    List<List<Integer>> res = new ArrayList<>();
    List<Integer>path = new ArrayList<>();
    boolean[]visited;
    public List<List<Integer>> permuteUnique(int[] nums) {
        Arrays.sort(nums);
        visited = new boolean[nums.length];
        backstracking(nums);
        return res;
    }
    public void backstracking(int[]nums){
        if(path.size() == nums.length){
            res.add(new ArrayList<>(path));
            return;
        }
        for(int i = 0; i < nums.length; i++){
            if(visited[i]) continue;
            if(i > 0 && nums[i] == nums[i-1] && !visited[i-1]){
                continue;
            }
            visited[i] = true;
            path.add(nums[i]);
            backstracking(nums);
            path.remove(path.size() - 1);
            visited[i] = false;
        }
    }
}

形式一、元素无重不可复选,即 nums 中的元素都是唯一的,每个元素最多只能被使用一次,backtrack 核心代码如下:

/ 组合/子集问题回溯算法框架 /

void backtrack(int[] nums, int start) {
    // 回溯算法标准框架
    for (int i = start; i < nums.length; i++) {
        // 做选择
        track.addLast(nums[i]);
        // 注意参数
        backtrack(nums, i + 1);
        // 撤销选择
        track.removeLast();
    }
}

/ 排列问题回溯算法框架 /

void backtrack(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        // 剪枝逻辑
        if (used[i]) {
            continue;
        }
        // 做选择
        used[i] = true;
        track.addLast(nums[i]);

        backtrack(nums);
        // 取消选择
        track.removeLast();
        used[i] = false;
    }
}

形式二、元素可重不可复选,即 nums 中的元素可以存在重复,每个元素最多只能被使用一次,其关键在于排序和剪枝,backtrack 核心代码如下:

Arrays.sort(nums);
/* 组合/子集问题回溯算法框架 */
void backtrack(int[] nums, int start) {
    // 回溯算法标准框架
    for (int i = start; i < nums.length; i++) {
        // 剪枝逻辑,跳过值相同的相邻树枝
        if (i > start && nums[i] == nums[i - 1]) {
            continue;
        }
        // 做选择
        track.addLast(nums[i]);
        // 注意参数
        backtrack(nums, i + 1);
        // 撤销选择
        track.removeLast();
    }
}
Arrays.sort(nums);
/* 排列问题回溯算法框架 */
void backtrack(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        // 剪枝逻辑
        if (used[i]) {
            continue;
        }
        // 剪枝逻辑,固定相同的元素在排列中的相对位置
        if (i > 0 && nums[i] == nums[i - 1] && !used[i - 1]) {
            continue;
        }
        // 做选择
        used[i] = true;
        track.addLast(nums[i]);

        backtrack(nums);
        // 取消选择
        track.removeLast();
        used[i] = false;
    }
}

形式三、元素无重可复选,即 nums 中的元素都是唯一的,每个元素可以被使用若干次,只要删掉去重逻辑即可,backtrack 核心代码如下:

/* 组合/子集问题回溯算法框架 */
void backtrack(int[] nums, int start) {
    // 回溯算法标准框架
    for (int i = start; i < nums.length; i++) {
        // 做选择
        track.addLast(nums[i]);
        // 注意参数
        backtrack(nums, i);
        // 撤销选择
        track.removeLast();
    }
}
/* 排列问题回溯算法框架 */
void backtrack(int[] nums) {
    for (int i = 0; i < nums.length; i++) {
        // 做选择
        track.addLast(nums[i]);

        backtrack(nums);
        // 取消选择
        track.removeLast();
    }
}

DP

子序列问题套路:
https://mp.weixin.qq.com/s/zNai1pzXHeB2tQE6AdOXTA

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值