首页 > 编程语言 >回溯算法体型归纳

回溯算法体型归纳

时间:2023-06-06 16:22:43浏览次数:75  
标签:used 组合 归纳 int nums 算法 candidates 回溯 path

回溯算法

回溯模板

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
    处理节点;
    backtracking(路径,选择列表); // 递归
    回溯,撤销处理结果
	}
}

例1:77. 组合

参数:begin代表用来记录本层每个节点递归的中,集合从哪里开始遍历(集合就是[1,...,n] )

n相当于树的宽度,k相当于树的深度

这个参数解决的问题就是选过元素还需不需要选,结合全排列问题考虑

for (int i = startIndex; i <= n; i++) { // 控制树的横向遍历
    path.push_back(i); // 处理节点 
    backtracking(n, k, i + 1); // 递归:控制树的纵向遍历,注意下一层搜索要从i+1开始
    path.pop_back(); // 回溯,撤销处理的节点
}

本题元素不能重复被选取,故要i+1。注意与例2的区别.

回溯法的搜索过程就是一个树型结构的遍历过程,在如下图中,可以看出for循环用来横向遍历,递归的过程是纵向遍历。

image-20220209135602859

例2:39. 组合总和

题还需要startIndex来控制for循环的起始位置,对于组合问题,什么时候需要startIndex呢?

  • 如果是一个集合来求组合的话,就需要startIndex,例如:77.组合 (opens new window),216.组合总和III (opens new window)。

  • 如果是多个集合取组合,各个集合之间相互不影响,那么就不用startIndex,例如:17.电话号码的字母组合(opens new window)

注意以上我只是说求组合的情况,如果是排列问题,又是另一套分析的套路,后面我再讲解排列的时候就重点介绍。

本题和77组合,216组合总和III的区别:本题元素可以重复选取,所以关键代码逻辑如下:

for (int i = begin; i < candidates.size(); i++) {
    sum += candidates[i];
    path.push_back(candidates[i]);
    backtracking(candidates, target, sum, i); // 关键点:不用i+1了,表示可以重复读取当前的数
    sum -= candidates[i];   // 回溯
    path.pop_back();        // 回溯
}

注意递归函数中的参数是i而不是i+1。用递归是纵向遍历来理解,递归遍历的是遍历”树的枝“,循环遍历的是‘’树的层“。

例3:40. 组合总和 II

来自代码随想录的解析理解

这道题目和例2的组合总和 如下区别:

  1. 本题candidates 中的每个数字在每个组合中只能使用一次。
  2. 本题数组candidates的元素是有重复的,而39.组合总和 (opens new window)是无重复元素的数组candidates。

最后本题和39.组合总和 (opens new window)要求一样,解集不能包含重复的组合。

这里有一个难点就是去重问题:

组合问题可以抽象为树形结构,那么“使用过”在这个树形结构上是有两个维度的,一个维度是同一树枝上使用过一个维度是同一树层上使用过

那么问题来了,我们是要同一树层上使用过,还是同一树枝上使用过呢?

回看一下题目,元素在同一个组合内是可以重复的,怎么重复都没事,但两个组合不能相同。

所以我们要去重的是同一树层上的“使用过”,同一树枝上的都是一个组合里的元素,不用去重

image-20220209145749142

此题还需要加一个bool型数组used,用来记录同一树枝上的元素是否使用过。这个集合去重的重任就是used来完成的。(后面还有一个不用加used数组的方法理解)

接下来的问题就是如何判断同一个树层上元素(相同的元素)是否使用过?

如果candidates[i] == candidates[i - 1] 并且 used[i - 1] == false,就说明:前一个树枝,使用了candidates[i - 1],也就是说同一树层使用过candidates[i - 1]

此时for循环里就应该做continue的操作。

image-20220209150056629

used[i-1]==false触发判断的前提条件是candidates[i] == candidates[i - 1],其余看蓝色字体部分的理解。

for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
    // used[i - 1] == true,说明同一树枝candidates[i - 1]使用过
    // used[i - 1] == false,说明同一树层candidates[i - 1]使用过
    // 要对同一树层使用过的元素进行跳过
    if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
        continue;
    }
    //做出选择
    ····
     backtracking(candidates, target, sum, i + 1, used); // 和39.组合总和的区别1:这里是i+1,每个数字在每个组合中只能使用一次
    ····
    //撤销选择
}

不用used数组的方法:

if (i > begin && candidates[i] == candidates[i - 1])                
	continue;            

i>begin的解释: (比较难理解)

image-20220209151012993

例4:17. 电话号码的字母组合

这题和上面的组合题不同之处在于它是在不同集合里面取元素,所以我们使用一个depth表示:每次递归都从不同数字映射出的元素去取。例如上图所示:当depth=0时,我们从数字‘2’映射出来的”abc“取,然后递归纵向遍历depth+1,我们从数字‘3’映射出来的“def”取。

for(int i=0;i<str.length();i++){//这里是i=0,不是i=depth,从不同集合取进行组合,for循环横向遍历,每次都从新集合里面从第一个开始取。
       temp.append(str.charAt(i));
       trackback(digits,numString,depth+1);//这里是depth+1,不是i+1,递归纵向遍历,depth+1取下一个集合的元素
       temp.deleteCharAt(temp.length()-1);
}

例5:131. 分割回文串

难点就是如何切割并且画出递归树。

for(int i=begin;i<s.length();i++){
            String subString=s.substring(begin,i+1);//substring()是切割字符串的左闭右开区间,所以要i+1.
            if(!ishuiwenString(subString))//如果不是回文字符串则跳过
                continue;
            path.add(subString);
            backtrack(s,i+1);//这里i+1是同一个位置不能重复切割,递归纵向遍历在下一位置进行切割
            path.removeLast();
        }

begin代表切割线,[begin,i]就是要截取的子串

例6:78. 子集

子集问题与组合和切割问题的区别:在树形结构中子集问题是要收集所有节点的结果,而组合问题是收集叶子节点的结果

image-20220212115527706

void backtracking(vector<int>& nums, int startIndex) {
        result.push_back(path); // 收集子集,要放在终止添加的上面,否则会漏掉自己
        if (startIndex >= nums.size()) { // 终止条件可以不加
            return;
        }
        for (int i = startIndex; i < nums.size(); i++) {
            path.push_back(nums[i]);
            backtracking(nums, i + 1);
            path.pop_back();
        }
    }

例7:90. 子集 II

那么关于回溯算法中的去重问题,在40.组合总和II (opens new window)中已经详细讲解过了,和本题是一个套路

image-20220214213644278

多加一个used数组即可。

例8:491.递增子序列

本题的由于题目要求的原因不能对原数组进行排序,所以无法使用前面去重的方法进行去重,本题使用哈希数组来进行去重。

image-20220214225811803

​ 同一父节点下的同层上使用过的元素就不能在使用了

int[] used = new int[201];// 这里使用数组来进行去重操作,题目说数值范围[-100, 100]
//这里used数组的创建是在for循环外,而不是在for循环内!
for (int i = start; i < nums.length; i++) {
     if (!path.isEmpty() && nums[i] < path.get(path.size() - 1) ||(used[nums[i] + 100] == 1)) 
         continue;
      used[nums[i] + 100] = 1;// 记录这个元素在本层用过了,本层同一个父节点后面不能再用了
      path.add(nums[i]);
      backtracking(nums, i + 1);
      path.remove(path.size() - 1);
}

标签:used,组合,归纳,int,nums,算法,candidates,回溯,path
From: https://www.cnblogs.com/sunshineTv/p/17460854.html

相关文章

  • 肖sir___杭州6月份面试__面试题归纳
    迪安科技  2023年6月6日  现场面试1.自我介绍2.为什么来杭州3.学物理为啥去做软件测试4.讲一下最近做的项目5.具体做的什么内容6.投保人多吗7.赔付比例多少8.怎么测试赔付金额准不准9.测试中那一块比较难10.团队有几个测试,项目组多少人11.测试流程12.测试计划包含哪些内容1......
  • 代码随想录算法训练营第二十八天|93. 复原 IP 地址
    【参考链接】93.复原IP地址【注意】1.切割问题就可以使用回溯搜索法把所有可能性搜出来。2.startIndex一定是需要的,因为不能重复分割,记录下一层递归分割的起始位置。3.本题我们还需要一个变量pointNum,记录添加逗点的数量。4.分割的段数作为终止条件。pointNum表示逗点数......
  • 【学习笔记】根号算法
    分块经典操作暴力思想先考虑最暴力的做法如何实现。平衡思想设长度\(n\),块长\(B\)。多数是定一个块长,使整块与散块、查询与修改的复杂度近似相等,并分别考虑整块好散块的情况。暴力重构指对散块处理时如果会破坏一个块的既有标记等等,可以选择暴力重新构建当前的标记。复......
  • 算法学习day44动态规划part06-518、377
    packageLeetCode.DPpart06;/***518.零钱兑换II*给你一个整数数组coins表示不同面额的硬币,另给一个整数amount表示总金额。*请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额,返回0。*假设每一种面额的硬币有无限个。*题目数......
  • 算法学习day45动态规划part07-322、279
    packageLeetCode.DPpart07;/***322.零钱兑换*给你一个整数数组coins,表示不同面额的硬币;以及一个整数amount,表示总金额。*计算并返回可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回-1。*你可以认为每种硬币的数量是无限的......
  • 算法学习day46动态规划part08-139
    packageLeetCode.DPpart08;importjava.util.HashSet;importjava.util.List;/***139.单词拆分*给你一个字符串s和一个字符串列表wordDict作为字典。请你判断是否可以利用字典中出现的单词拼接出s。*注意:不要求字典中出现的单词全部都使用,并且字典中的单词......
  • 算法学习day48动态规划part09-377、213、198
    packageLeetCode.DPpart09;/***377.组合总和Ⅳ*给你一个由不同整数组成的数组nums,和一个目标整数target。请你从nums中找出并返回总和为target的元素组合的个数。*题目数据保证答案符合32位整数范围。*示例:*输入:nums=[1,2,3],target=4*输......
  • 算法学习day42动态规划part04-416
    packageLeetCode.DPpart04;/***416.分割等和子集*给你一个只包含正整数的非空数组nums。请你判断是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。*示例:*输入:nums=[1,5,11,5]*输出:true*解释:数组可以分割成[1,5,5]和[11]。**/......
  • 算法学习day43动态规划part05-1049、474、494
    packageLeetCode.DPpart05;/***1049.最后一块石头的重量II*有一堆石头,用整数数组stones表示。其中stones[i]表示第i块石头的重量。*每一回合,从中选出任意两块石头,然后将它们一起粉碎。假设石头的重量分别为x和y,且x<=y。那么粉碎的可能结果如下:*如果x=......
  • 算法 in Go:Binary Search(二分查找)
    算法inGo:BinarySearch(二分查找)BinarySearch(二分查找)BinarySearch(二分查找)猜数1、2、3、4、5、6、7、8排好序一个集合,先从中间开始猜,根据提示就可以排除一半,在剩余的一半里,再从中间开始猜,依此类推,这就是二分查找。BinarySearch(二分查找)接收什么参数,返回什么值输入:......