很多问题动态规划比较难想,dfs就可以用来兜底,再加上合适的剪枝就成了回溯,有时候可能比dp更快,dfs感觉比递归更好理解,一般都可以使用二叉树或者图的方式来理解。一般用回溯能解的一定能用dfs解因为dfs是遍历了所有的解。回溯遍历的解是dfs的子集
目录
01背包问题
dfs求解
题目来源:https://www.acwing.com/problem/content/2/
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
//dfs 显然超时
int w[MAXN]; // 重量
int v[MAXN]; // 价值
int f[MAXN]; // f[i][j], j重量下前i个物品的最大价值
int maxprofit = 0;
void dfs(int w[],int v[],int i,int tmpw,int tmpv,int n,int m){
if(i==n){ //终止条件
if(tmpv > maxprofit && tmpw <= m){
maxprofit = tmpv;
}
return ;//无论如何这里都要出去
}
if(tmpw > m){ //对不合理的解进行剪枝
return ;
}
dfs(w,v,i+1,tmpw,tmpv,n,m);//不取第i件物品
dfs(w,v,i+1,tmpw+w[i],tmpv+v[i],n,m);//取第i件物品
}
int main()
{
int n, m;
cin >> n >> m;
for(int i = 0; i < n; ++i)
cin >> w[i] >> v[i];
dfs(w,v,0,0,0,n,m);
cout<<maxprofit<<endl;
return 0;
}
不用修改的变量都可以放到dfs的外面
#include<bits/stdc++.h>
using namespace std;
const int MAXN = 1005;
//超时
int w[MAXN]; // 重量
int v[MAXN]; // 价值
int n,m;
int maxprofit = 0;
void dfs(int i,int tmpw,int tmpv){
if(i==n){
if(tmpv > maxprofit && tmpw <= m){
maxprofit = tmpv;
}
return ;
}
if(tmpw > m){
return ;
}
dfs(i+1,tmpw,tmpv);
if(tmpw + w[i] <=m)
dfs(i+1,tmpw+w[i],tmpv+v[i]);
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; ++i)
cin >> w[i] >> v[i];
dfs(0,0,0);
cout<<maxprofit<<endl;
return 0;
}
回溯求解
回溯有多种写法,由于回溯其实就是dfs实现的,我更倾向于直接根据上面的dfs写回溯,这样容易理解,而且可以作为模板套用在其他问题上,但是很遗憾这个题回溯有个别测试案列还是超时的,关键是掌握这种思想
扫描二维码关注公众号,回复:
12406101 查看本文章

回溯模板
//这个状态把只需要修改的变量写进来,其他的都可以作为全局变量
void dfs(当前状态){
if 当前状态到达边界
更新解
return ; // 一定不能忘掉
if 当前状态不满足约束条件 //剪枝
return ; //一定不能忘掉
for 遍历所有下一个可能的状态
产生下一个新的状态
dfs(下一个新的状态)
回到上一个状态
}
回溯法求解01背包
int w[MAXN]; // 重量
int v[MAXN]; // 价值
int n,m;
int maxprofit = 0;
//i 第i+1个物品
//tmpw 当前放入背包的总重量
//tmpv 当前放入背包的总价值
void dfs(int i,int tmpw,int tmpv){ // 状态 (i,tmpw,tmpv)
if(i==n){ //当前状态到达边界条件
if(tmpv > maxprofit && tmpw <= m){
maxprofit = tmpv;
}
return ;
}
if(tmpw > m){ //当前状态不满足约束条件
return ;
}
for(int k=0;k<2;k++){ //产生下一个新的状态 ,选或不选
tmpw +=k*w[i]; //产生新的状态
tmpv +=k*v[i];
dfs(i+1,tmpw,tmpv);
tmpw -=k*w[i];//回到上一个状态回溯
tmpv -=k*v[i];
}
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; ++i)
cin >> w[i] >> v[i];
dfs(0,0,0);
cout<<maxprofit<<endl;
return 0;
}
回溯+限定条件剪枝
int w[MAXN]; // 重量
int v[MAXN]; // 价值
int n,m,tmpw,tmpv;
int maxprofit = 0;
int Bound(int i){ //限定条件
int s = 0;
for(int j=i;j<n;j++){
s +=v[j];
}
return s+tmpv;
}
void dfs(int i){
if(i==n){
if(tmpv > maxprofit && tmpw <= m){
maxprofit = tmpv;
}
return ;
}
if(Bound(i+1) > maxprofit){ //满足限制条件搜索右子树
dfs(i+1);
}
if(tmpw + w[i] <= m){ //满足约束条件搜索左子树
tmpw +=w[i]; //产生新的状态
tmpv +=v[i];
dfs(i+1);
tmpw -=w[i];
tmpv -=v[i];
}
}
int main()
{
cin >> n >> m;
for(int i = 0; i < n; ++i)
cin >> w[i] >> v[i];
dfs(0);
cout<<maxprofit<<endl;
return 0;
}
回溯算法的基础题目是组合排列子集问题,比较难的就是搜索
1291. 顺次数
时间打败100% 内存 5%
题目
我们定义「顺次数」为:每一位上的数字都比前一位上的数字大 1 的整数。
请你返回由 [low, high] 范围内所有顺次数组成的 有序 列表(从小到大排序)。
示例 1:
输出:low = 100, high = 300
输出:[123,234]
示例 2:
输出:low = 1000, high = 13000
输出:[1234,2345,3456,4567,5678,6789,12345]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/sequential-digits
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路
很显然这些数只能由1-9组成,所以相当于求1-9的排序数,而且每个数比前一个数大一。一般这些求所有可能的组合,排列和子集问题都是用回溯解决的。
class Solution {
public:
vector<int>solu;
vector<int>tmp;
vector<int> sequentialDigits(int low, int high) {
dfs(1,low,high,0);
sort(solu.begin(),solu.end());
return solu;
}
int num(vector<int>tmp){ //计算由这些数组成的整数
int s = 0;
for(int i=0;i<tmp.size();i++){
s+=tmp[i]*pow(10,tmp.size()-1-i);
}
return s;
}
void dfs(int i, int low,int high,int pre){ //当前状态变量 i,pre记录前一个状态变量
if(num(tmp)>=low && num(tmp)<=high){
solu.push_back(num(tmp));
}
for(int j=i;j<10;j++){ //遍历下一个所有可能状态 i+1-9
if(tmp.size()<1){//第一个数直接放入
tmp.push_back(j);
pre = j;
dfs(j+1,low,high,pre);//遍历下一个所有可能状态 i+1-9
tmp.pop_back();
//pre = j-1; //我开始把这个加上了,这个其实不应该加,因为第一个数之前没有数
}
if(j-pre==1){
tmp.push_back(j);
pre = j;
dfs(j+1,low,high,pre);
tmp.pop_back();
pre = j-1;
}
}
}
};
22. 括号生成
题目
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例:
输入:n = 3
输出:[
"((()))",
"(()())",
"(())()",
"()(())",
"()()()"
]
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/generate-parentheses
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
解题思路
求组合显然可以用回溯求解,
class Solution {
public:
vector<string>solu;
//在string后面加入char用push_back
//加入string用+
string s="";
vector<string> generateParenthesis(int n) {
dfs(n,0,0,"");
return solu;
}
void dfs(int n,int l,int r,string s){//当前已经状态左括号和右括号使用的数量
if(l ==n && r==n){ //边界条件
solu.push_back(s);
return ;
}
if(r > l || r > n || l> n){ //不满足约束条件,剪枝
return ;
}
//下面三种写法都可以
//写法一:
/*
s.push_back(ch[0]);
dfs(n,l+1,r,s);
s.pop_back();
s.push_back(ch[1]);
dfs(n,l,r+1,s);
s.pop_back();
*/
//写法二
/*
for(int k=0;k<2;k++){
if(k==0){
s.push_back('(');
l +=1;
dfs(n,l,r,s);
l -=1;
}
else{
s.push_back(')');
r +=1;
dfs(n,l,r,s);
r -=1; //状态重置,这里r,和l需要重置的原因是,r,l在dfs里面修改了
}
s.pop_back();
}
*/
//写法三
for(int k=0;k<2;k++){ //遍历下一个可能的状态,使用左括号或者右括号
if(k==0){ //使用左括号
s.push_back('(');
dfs(n,l+1,r,s);
}
else{ //使用右括号
s.push_back(')');
dfs(n,l,r+1,s);
}
s.pop_back(); //状态重置,这里r,和l不需要重置的原因是,r,l在dfs里面没有修改
}
}
};