目录

回溯算法总结

回溯算法总结

一、回溯模板回顾

void backTracking() {
    if(终止条件){
        收集节点
        return;
    }
    for(广度遍历) {
        处理节点
        backTracking();
        回溯(和处理节点正好相反)
    }
    return;
}

二、回溯可以解决的问题

模板总结

限定组合的元素数量/限定组合的相加和——>if终止条件

数组中无重复元素,组合不能重复且每个元素只能使用一次——>从startIndex(每次递归时是自身下一个元素)开始,到末尾结束

数组中无重复元素,组合不能重复且每个元素可以使用多次——>startIndex每次从自身开始,到末尾结束

数组中有重复元素,组合不能重复且每个元素只能使用一次——>排序,used数组限定树层去重,树枝不去重

排列问题——>不能设置startIndex数组,方案1:每次访问元素的时候在path中寻找,看看是否有这个元素,有则不访问;方案2:设置used数组,深度递归时,如果used为true,那么说明该元素访问过,跳过

1.组合问题

77组合

https://i-blog.csdnimg.cn/direct/c59f6bf5b69d4a03bbea9360cd872871.png

限定了组合的元素数量,组合不能重复,每个元素只能用一次。

if终止条件为组合长度为k;for循环条件为了保证组合不出现重复,应该从startIndex开始,到n结束;for中为向path中添加元素、递归、回溯。

216组合总和III

https://i-blog.csdnimg.cn/direct/7c1934be5c374fedb594b8d7dfbde085.png

限定组合元素数量和相加和,组合不能重复,组合中每个元素只能用一次

if终止条件为组合长度为k且相加和为n;for循环条件为了保证组合不能重复且每个元素只能用一次,从startIndex开始,到9结束;for中为向path添加元素、递归、回溯

39组合总和

https://i-blog.csdnimg.cn/direct/a3225d3ae4ba495c8dda2e5cbc679149.png

无重复元素,限定组合元素的相加和,组合不能重复,组合中每个元素可以使用多次

if终止条件为相加和为target;for循环条件因为每个元素可以使用多次,且组合不能重复,所以也没必要从0开始,直接每层循环从startIndex开始即可,不过上面几道题目startIndex是每次都是本元素的下一个元素,保证每个元素只能使用一次,本题目只需要每次startIndex都从本元素开始即可,这样一个元素就可以使用多次,且不会和前面的组合重复;for循环体向path中添加元素、递归、回溯。

40组合总和II

https://i-blog.csdnimg.cn/direct/d56cab84263f4259a7609fb5b8835ab8.png

有重复元素(示例中有),限定元素相加和,组合不能重复,组合中每个元素只能使用一次

if终止条件为相加和为target;先对数组进行排序,这样才能使用used数组,for循环条件因为每个元素只能使用一次且组合不能重复,所以设置startIndex,每次startIndex从自身的下一个元素开始,组合又不能重复,所以得设置used数组,限定树层重复元素就跳过,树枝重复元素允许继续,used初始化为false,深度递归也就是for循环体内递归的时候,将该数字的used设置为true,允许继续访问。

17电话号码的字母组合

https://i-blog.csdnimg.cn/direct/c12a6866efc549a1bd0a58d0e4e635d6.png

这道题目涉及到了具体的情境,和上面几道题不太一样。限定组合元素长度,每个元素可以使用多次。

if终止条件为长度和数字长度相等,每次取元素都和上次无关,所以for循环条件从startIndex开始,到该数字代表的字母末尾结束。

2.切割问题

切割问题的关键在于如何模拟切割线,找到子串。可以用startIndex来模拟切割线,startIndex所指向的元素的后面那个空,实际上就是切割线。切割一次,然后在后面继续切割即可,每次只需要继续切割,然后进行判断,如果是回文串,那么就将这个子串加入到集合中。

切割问题相比组合问题,要注意的点少很多。

131分割回文串

https://i-blog.csdnimg.cn/direct/6310690e4e06414c82a5803936b13f46.png

if终止条件为已经切割到了末尾,也就是startIndex已经指向了最后一个元素。for循环遍历条件,因为每次切割都需要从上次切割的后面来进行切割,所以需要设置startIndex,每次指向它的下一个元素;for循环体,判断新切割的子串是否是回文串、递归、回溯。

93复原IP地址

https://i-blog.csdnimg.cn/direct/27ce0dc2993f45e2a7c81c7b95a276d0.png

实质上也是切割子串,对每个子串的要求是不能有别的字符,在0-255之间。

if终止条件,分割三次即为结束;for循环遍历条件仍然是需要设置startIndex,从本元素的下一个元素开始分割,判断此次分割的子串是否是合法的ip地址;for循环处理条件,如果是合法的ip地址,那么就加入一个点,并且继续分割。

3.子集问题

子集问题的关键点在于:收集的是路径上的所有节点,而不单单是叶子节点。所以if中收集了一个节点不能写return。

78子集

https://i-blog.csdnimg.cn/direct/ca9fc5c24f234aebabb35f975d915ab9.png

没有重复元素,子集不能重复,每个元素只能使用一次

if终止条件,因为收集的是路径上的所有节点,所以不需要进行判断,直接将所有节点全部收集,收集到数组末尾就结束,此时startIndex=nums.size()。for循环条件,每个元素只能使用一次,所以需要设置startIndex。

90子集II

https://i-blog.csdnimg.cn/direct/9ac8f6730ace4905b69b9125d339bfd7.png

有重复元素,子集不能重复,每个元素只能使用一次。

if终止条件收集到数组末尾就结束,此时startIndex=nums.size()。有重复元素,需要先将数组排序。for循环条件,每个元素只能使用一次,需要设置startIndex;for循环处理,树层去重,树枝不去重,设置used。

491非递减子序列

https://i-blog.csdnimg.cn/direct/c034678b656143e8a3a89fee707bee48.png

有重复元素,序列中至少有两个元素,子序列不重复,每个元素使用一次

if终止条件,如果序列长度大于等于2,那么就将其加入结果数组中。for循环条件,每个元素使用一次,需要设置startIndex;for循环处理,递增则加入,树层去重,树枝不去重,设置used或者设置uset记录广度中是否使用过该元素。

4.排列问题

46全排列

https://i-blog.csdnimg.cn/direct/005960b675084d51bf6fab069daa9d6d.png

不含重复数字,限定排列长度,排列不能重复,每个元素只能使用一次。

if终止条件为长度达到了数组长度;for循环条件为每次从0开始,但是要判断这个元素是否已经访问过,如果访问过了,跳过本次广度遍历;for循环体将元素加入path、递归、回溯。

47全排列II

https://i-blog.csdnimg.cn/direct/764b939ac42d4b17b16ffdbbc82586f2.png

包含重复数字,限定排列长度,排列不能重复,每个元素只能使用一次。

if终止条件为长度达到了数组长度;因为排列不能重复,但是又有重复元素,所以需要将数组先排序, 排列类的题目不能设置startIndex,否则访问不了全部的数组元素,

然后利用used数组,一方面是重复元素进行树层去重,同时树枝遇到了重复元素可以继续访问;另一方面是为了检测本元素是否使用过,如果使用过,那么在深度递归的时候,该元素的used值应当就是true,此时也跳过本次访问;for循环体中为将元素加入path、递归、回溯。

5.棋盘问题(暂时跳过)