【算法攻坚】回溯模板解题

简介: 【算法攻坚】回溯模板解题

今日题目


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


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


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


示例 1:


输入: candidates = [2,3,6,7], target = 7 输出: [[7],[2,2,3]]


示例 2:


输入: candidates = [2,3,5], target = 8 输出: [[2,2,2,2],[2,3,3],[3,5]]


示例 3:


输入: candidates = [2], target = 1 输出: []


示例 4:


输入: candidates = [1], target = 1 输出: [[1]]


示例 5:


输入: candidates = [1], target = 2 输出: [[1,1]]


提示:

1 <= candidates.length <= 30 1 <= candidates[i] <= 200 candidate 中的每个元素都是独一无二的。 1 <= target <= 500


思路


这道题是典型的回溯思想寻找最优解的题目,所以还是尝试按照通用模板去解题

解决一个回溯问题,实际上就是一个决策树的遍历过程。只需要思考 3 个问题:


  • 1、路径:也就是已经做出的选择。
  • 2、选择列表:也就是你当前可以做的选择。
  • 3、结束条件:也就是到达决策树底层,无法再做选择的条件。


伪代码实现方面,回溯算法的框架:


result = []
backtrack(路径, 选择列表):
    if 满足结束条件:
        result.add(路径)
        return
for 选择 in 选择列表:
    做选择
    backtrack(路径, 选择列表)
    撤销选择


这个模板的核心就是 for 循环里面的递归,在递归调用之前做选择,在递归调用之后撤销选择,特别简单。


第一步实现全排列输出:


  • 1、路径:用有序链表存储
  • 2、选择列表:也就是要遍历的元素数组
  • 3、结束条件:链表长度达到最大的数组长度


List<List<Integer>> res = new LinkedList<>();
/* 主函数,输入一组不重复的数字,返回它们的全排列 */
List<List<Integer>> permute(int[] nums) {
    // 记录「路径」这里的 选择列表 即包含在nums中
    LinkedList<Integer> track = new LinkedList<>();
    backtrack(nums, track);
    return res;
}
// 路径:记录在 track 中
// 选择列表:nums 中的元素
// 结束条件:nums 中的元素全都在 track 中出现
void backtrack(int[] nums, LinkedList<Integer> track) {
    // 触发结束条件
    if (track.size() == nums.length) {
        res.add(new LinkedList(track));
        return;
    }
    for (int i = 0; i < nums.length; i++) {
        // 排除不合法的选择
        if (track.contains(nums[i]))
            continue;
        // 做选择
        track.add(nums[i]);
        // 进入下一层决策树
        backtrack(nums, track);
        // 取消选择,返回上一层决策树
        track.removeLast();
    }
}


通过上述模板代码实现后,只是实现全排列组合输出,


下一步实现,等于正整数 target的全排列:


  • 1、路径:用有序链表存储
  • 2、选择列表:也就是要遍历的元素数组
  • 3、结束条件:链表中元素之和等于target
  • 4、剪枝:元素排序后,如果当前元素大于target,后面也不会再满足,所以直接结束


public List<List<Integer>> res =  new LinkedList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
    LinkedList<Integer> track = new LinkedList<>();
    // 排序的原因是在回溯的时候比较容易剪枝
    Arrays.sort(candidates);
    // 套用上面的公式,candidates是指选择列表,target用来判断是否结束以及用于剪枝
    // track则是路径,即已经做出的选择
    backtrack(candidates, target, track);
    return res;
}
private void backtrack(int[] candidates, int target, LinkedList<Integer> track) {
    //先判断结束条件
    if (target < 0) {
        return;
    }
    if (target == 0){
        // 当target等于0的时候,将路径加入到结果列表中
        res.add(new LinkedList<>(track));
        return;
    }
    // 遍历选择列表,这里没有去重
    //下面会设置start,每次递归的时候只在candidates中当前及之后的数字中选择。
    for(int i = 0;i < candidates.length;i++){
        // 这就是排序的好处,可以直接这样剪枝,否则还得遍历
        if(target < candidates[i]){
            break;
        } 
        track.add(candidates[i]);
        backtrack(candidates,target-candidates[i],track);
        track.removeLast();
    }
}


上述代码实现了等于target的组合输出,但是并没有达到题目要求的去除重复组合的要求,输出的结果中会有重复的组合元素,所以下一步考虑去重,也就是每次不再从0开始递归元素,都是从当前节点往后进行选取,这样就不会再出现获取前面已经组合过的元素


public List<List<Integer>> res =  new LinkedList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
    LinkedList<Integer> track = new LinkedList<>();
    Arrays.sort(candidates);
    backtrack(candidates, 0, target, track);
    return res;
}
private void backtrack(int[] candidates, int start, int target, LinkedList<Integer> track) {
    if (target < 0) {
        return;
    }
    if (target == 0){
        res.add(new LinkedList<>(track));
        return;
    }
    //int i= start去重组合的关键
    for(int i = start;i < candidates.length;i++){
        if(target < candidates[i]) {
            break;
        }
        track.add(candidates[i]);
        backtrack(candidates,i,target-candidates[i],track);
        track.removeLast();
    }
}


执行用时:2 ms, 在所有 Java 提交中击败了99.65%的用户

内存消耗:38.8 MB, 在所有 Java 提交中击败了21.05%的用户


小结


回溯算法解题整体都是有着类似的目的,都是在寻找所有可行解的题,我们都可以尝试用搜索回溯的方法来解决。同时要记住算法实现的基本框架,可以大大减少实现难度。


目录
相关文章
|
1月前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
数据结构与算法系列学习之串的定义和基本操作、串的储存结构、基本操作的实现、朴素模式匹配算法、KMP算法等代码举例及图解说明;【含常见的报错问题及其对应的解决方法】你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习(8)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之顺序表【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找等具体详解步骤以及举例说明
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
存储 Web App开发 算法
2024重生之回溯数据结构与算法系列学习之单双链表【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构之单双链表按位、值查找;[前后]插入;删除指定节点;求表长、静态链表等代码及具体思路详解步骤;举例说明、注意点及常见报错问题所对应的解决方法
|
1月前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之栈和队列精题汇总(10)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第3章之IKUN和I原达人之数据结构与算法系列学习栈与队列精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
算法 安全 NoSQL
2024重生之回溯数据结构与算法系列学习之顺序表习题精讲【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找习题精讲等具体详解步骤以及举例说明
|
1月前
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构的基本概念;算法的基本概念、特性以及时间复杂度、空间复杂度等举例说明;【含常见的报错问题及其对应的解决方法】
|
1月前
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之王道第2.3章节之线性表精题汇总二(5)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
IKU达人之数据结构与算法系列学习×单双链表精题详解、数据结构、C++、排序算法、java 、动态规划 你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
1月前
|
算法
”回溯算法“框架及练习题
”回溯算法“框架及练习题
42 0