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

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

今日题目


给定一个无重复元素的正整数数组 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%的用户


小结


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


目录
相关文章
|
11月前
|
机器学习/深度学习 算法 C++
【DFS/回溯算法】2016年蓝桥杯真题之路径之谜详解
题目要求根据城堡北墙和西墙箭靶上的箭数,推断骑士从西北角到东南角的唯一路径。每步移动时向正北和正西各射一箭,同一格不重复经过。通过DFS回溯模拟“拔箭”过程,验证路径合法性。已知箭数约束路径唯一,最终按编号输出行走顺序。
|
算法
回溯算法的基本思想
本节介绍回溯算法,通过图1中从A到K的路径查找示例,说明其与穷举法的异同。回溯算法通过“回退”机制高效试探各种路径,适用于决策、优化和枚举问题。
429 0
|
算法 Java
算法系列之回溯算法求解数独及所有可能解
数独求解的核心算法是回溯算法。回溯算法是一种通过逐步构建解决方案并在遇到冲突时回退的算法。具体来说,我们尝试在空格中填入一个数字,然后递归地继续填充下一个空格。如果在某个步骤中发现无法继续填充,则回退到上一步并尝试其他数字。
871 11
算法系列之回溯算法求解数独及所有可能解
|
存储 缓存 算法
什么是回溯算法
回溯算法是一种通过尝试所有可能路径寻找问题解的策略,采用深度优先搜索与状态重置机制。它适用于组合、排列、棋盘等需枚举所有可能解的问题,核心思想包括DFS遍历、剪枝优化与状态恢复。尽管时间复杂度较高,但通过合理剪枝可显著提升效率,是解决复杂搜索问题的重要方法。
873 0
|
算法 Java
算法系列之回溯算法
回溯算法(Backtracking Algorithm)是一种通过穷举来解决问题的方法,它的核心思想是从一个初始状态出发,暴力搜索所有可能的解决方案,遇到正确解将其记录,直到找到了所有的解或者尝试了所有的可能为止。
964 4
算法系列之回溯算法
|
算法 安全 搜索推荐
套用算法模板备案审核问题增多的原因及解决建议
随着算法备案要求的完善,企业常因使用网上廉价模板而遭遇审核通过率低、问题增多的困境。本文分析了审核不通过的原因,包括模板缺乏针对性、审核标准严格、审核人员主观差异及企业准备不足等,并提出建议:深入了解备案要求、准备详尽材料、避免通用模板、寻求专业帮助。备案后还需持续合规管理,确保算法服务安全运行。
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
数据结构与算法系列学习之串的定义和基本操作、串的储存结构、基本操作的实现、朴素模式匹配算法、KMP算法等代码举例及图解说明;【含常见的报错问题及其对应的解决方法】你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
2024重生之回溯数据结构与算法系列学习之串(12)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丟脸好嘛?】
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习(8)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!
|
存储 算法 安全
2024重生之回溯数据结构与算法系列学习之顺序表【无论是王道考研人还真爱粉都能包会的;不然别给我家鸽鸽丢脸好嘛?】
顺序表的定义和基本操作之插入;删除;按值查找;按位查找等具体详解步骤以及举例说明
|
算法 安全 搜索推荐
2024重生之回溯数据结构与算法系列学习之单双链表精题详解(9)【无论是王道考研人还是IKUN都能包会的;不然别给我家鸽鸽丢脸好嘛?】
数据结构王道第2.3章之IKUN和I原达人之数据结构与算法系列学习x单双链表精题详解、数据结构、C++、排序算法、java、动态规划你个小黑子;这都学不会;能不能不要给我家鸽鸽丢脸啊~除了会黑我家鸽鸽还会干嘛?!!!

热门文章

最新文章