代码随想录——回溯

系列文章目录

代码随想录——回溯



概述

回溯的本质就是递归遍历,但在完成某一条路之后会撤回到上一层,然后重新选择另一条路继续遍历,是一个比较低效的算法,能进行提升的方式就是剪枝。

void backtracking(参数) {
    if (终止条件) {
        存放结果;
        return;
    }

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

在这里插入图片描述

组合

组合

链接:https://leetcode.cn/problems/combinations/description/

vector<vector < int > > 作为最终返回的结果,vector < int >作为每一小项,中止条件是vector< int > == k,注意在递归过程中n,k是不变的,所以必须引入一个递增index来去重。

剪枝方法:

  1. 已经选择的元素个数:path.size();
  2. 还需要的元素个数为: k - path.size();
  3. 在集合n中至多要从该起始位置 : n - (k - path.size()) + 1,开始遍历

组合III

链接:https://leetcode.cn/problems/combination-sum-iii/description/

与前一个组合几乎一样,但中止条件需要加一个total == k的判断。

电话号码的字母组合

链接:https://leetcode.cn/problems/letter-combinations-of-a-phone-number/description/

中止条件是单个项的size等于digits的size,一开始我以为要双循环,即digits遍历和单个数字对应的字母遍历,但其实不需要,因为digits一定是顺序往后取的,所以只需要定义一个index,每层递归+1即可。
ps:在leetcode由char转变为int的方法是减去 ‘0’

num[digits[index] - '0'

组合总和

链接:https://leetcode.cn/problems/combination-sum/description/
中止条件为当总和相等时push进result并return,总和超过target时直接人return;注意这道题也是不能重复的,一般不能重复的都需要加入index来保证起始位置。

组合总和II

链接:https://leetcode.cn/problems/combination-sum-ii/description/
这道题不重复的核心点在于,同层的数不能够重选,例如[1,1,2,2,5],第一层选了1,第二层还是可以继续选1,因为两者不是同层的。但如果第一层的第一个1用完了,又选了第二个1,这就重复了,所以只需要加一个判断条件:

            if (i > index && candidates[i] == candidates[i - 1]) {
                continue;
            }

这里的核心点在于ℹi > index,因为index是本层的第一个数,必然不可能重复,所以要从index下一个开始判断,此时如果第二个数和第一个数是相同的,则属于同层重复选择,直接continue;

分割

分割回文串

https://leetcode.cn/problems/palindrome-partitioning/description/
每切下来一个子字符串,就相当于是在一层做了选择,这样就可以套上回溯算法的模板。中止条件是起始切割位置到了字符串的最后一位,额外再写一个回文判断函数。、
注意:leetcode里常用的切割字符串函数substr,第一个参数是起始位置,第二个参数是要切割的长度。

string str = s.substr(index,i - index + 1);

** 复原ip地址

https://leetcode.cn/problems/restore-ip-addresses/description/
这道题一直做错,能接受的方法是,将切割每一个子字符串进行判断,符合标准后放入vector备用,个数达到4个并且index也等于s.size()之后,再拼接放入最终的result里。
注意:string转为int直接用stoi函数

return stoi(s) <= 255;

字符串的增加和删改不能用-=和+=,要用insert和erase。

子集

子集

https://leetcode.cn/problems/subsets/description/
这道题唯一要注意的点是子集的大小是不固定的,所以不能在中止条件里面将子集添加到结果里,每一次循环都要添加,所以放在递归的开头。中止条件是index == s.size()。

子集II

https://leetcode.cn/problems/subsets-ii/description/
这题和之前组合的去重思路一模一样,在子集的基础上,首先对nums进行sort保证是递增的,然后进行判断去掉本层重复的选择。

if(i > index && nums[i] == nums[i - 1]) continue;

全排列

全排列

https://leetcode.cn/problems/permutations/description/
全排列不需要用到index,index只有不能从头重复拿的问题需要用的,全排列需要从头拿,但怎么保证去重呢,就把取过的数暂存到temp,并将这个数重新赋值一个不会取到的数,回溯过程中再放回来。循环过程中,对数进行判断,如果是那个不会取到的数,就直接跳过,从而达到去重的效果。

全排列II

https://leetcode.cn/problems/permutations-ii/description/
和之前的去重操作一样,先sort然后本层去重。

棋盘问题

N皇后

https://leetcode.cn/problems/n-queens/description/
比较好的思路是先创建一个n*n的棋盘,每一行放置Q的时候,对这个位置进行判断,判断函数的只需要判断这一层之上是否满足就可以了,中止条件是层数 == n。
注意:

  1. 引用传递会比值传递节省巨幅的空间和时间,尽量使用引用传递。
  2. for循环的判断条件,用逗号时相当于只有最后一个生效!!!
for (int i = 5, j = 3; i >= 0, j >= 0; i--, j--) {
    // 在这里,条件实际上是 j >= 0
    // 循环体
    cout << "i: " << i << ", j: " << j << endl;
}
for (int i = 5, j = 3; i >= 0 && j >= 0; i--, j--) {
    // 在这里,条件是 i >= 0 && j >= 0
    // 循环体
    cout << "i: " << i << ", j: " << j << endl;
}

***解数独

https://leetcode.cn/problems/sudoku-solver/
写的不好。
这题思路回溯的思想是写两个for循环,然后相当于对每一个空的格子尝试填入数字,如果满足条件则return true,如果9个数字都不满足则回溯。填入数字之前先判断。

其他

***递增子序列

https://leetcode.cn/problems/non-decreasing-subsequences/description/
这道题最大的问题在于不能够sort,并且得保证去重。正常的想法是只要path数量大于2,就放入result。但这样就给去重造成了困难,所以一个很新颖的思路是只有当index == size的时候,才去考虑将结果放入result,这样做的好处是第一个判断条件只要元素大于等于path的最后一个数,就正常迭代,但迭代完成回溯回来的时候,加入了第二个判断条件,即如果元素等于path的最后一个数,因为第一个条件只要大于等于就满足,如果第二个条件判断元素为等于,则说明该元素和上一个元素是相等的,即为同层的重复元素,则直接跳过。非常好的想法!

重新安排行程

https://leetcode.cn/problems/reconstruct-itinerary/description/
还没写。

  • 32
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值