目录
一:递归+回溯
题目:给定一个二叉树的根节点 root ,和一个整数 targetSum ,求该二叉树里节点值之和等于targetSum的路径的数目。
路径不需要从根节点开始,也不需要在叶子节点结束,但是路径方向必须是向下的
解题需要明确:
- 因为结点值可以是负数,所以该节点只要不是叶子结点,即使值相等也要递归。
- 因为必须父->孩子,不可以出现孩子->兄弟,所以会用到回溯
class Solution { private: //注意这里存储前缀和的测试用例数据大,最好改成long /*全局变量*/ map<long,int> record;//record[前缀和:presum]=出现频次 /*私有函数实现*/ int findAsPresum(TreeNode* root, int targetSum,long presum){ if(root==NULL) return 0; //计算前缀和 presum+=root->val; //定义返回结果 int res=0; //计算以当前结点为根符合条件的路径 if(record.find(presum-targetSum)!=record.end())//找到了 res+=record[presum-targetSum]; //递归前需要加上访问过的结点presum record[presum]++; //递归 res+=findAsPresum(root->left,targetSum,presum); res+=findAsPresum(root->right,targetSum,presum); //执行该语句时说明这是叶子结点---路径终点 //需要回溯,因为不可以跨路径 record[presum]--; return res; } public: int pathSum(TreeNode* root, int targetSum) { //初始化私有变量record record[0]=1;//保证起点 return findAsPresum(root,targetSum,0);//开始没有结点 } };
题目思路:
- 累计每一条路径二叉树的结点前缀和,然后在哈希表中存储,每次遇到新结点,去查找表找有没有key值等于:加上该节点后的前缀和-目标值。如果查找表有,就找到了路径。
- 然后更新查找表,递归地找路径的下一个节点,累计路径和。
- 注意题目的路径要求自上而下,如果该节点是叶子结点,需要不断回退,查找表要不断删去包含了该节点的前缀值。
这里:
目标值是不变的,可以设为私有变量,也可以视为传参,每次赋相同值。这里的哈希表map和前缀和需要更新,必须作为新的函数参数,当然也可以设为私有变量。
class Solution { private: int target; /*私有函数实现*/ int findAsPresum(TreeNode* root,long presum,map<long,int>& record){ if(root==NULL) return 0; //计算前缀和 presum+=root->val; //定义返回结果 int res=0; //计算以当前结点为根符合条件的路径 if(record.find(presum-target)!=record.end())//找到了 res+=record[presum-target]; //递归前需要加上访问过的结点presum record[presum]++; //递归 res+=findAsPresum(root->left,presum,record); res+=findAsPresum(root->right,presum,record); //执行该语句时说明这是叶子结点---路径终点 //需要回溯,因为不可以跨路径 record[presum]--; return res; } public: int pathSum(TreeNode* root, int targetSum) { map<long,int> record; record[0]=1;//保证起点 target=targetSum; return findAsPresum(root,0,record);//开始没有结点 } };
传哈希表---注意加&,因为它在运行过程中不断改变。
双递归解法
class Solution { private: int nodeinPath(TreeNode* node, long target){ if(node==NULL) return 0; int res=0; if(node->val==target) res+=1;//找到符合条件,不管是不是叶子结点,路径++ //寻找是否成立 res+=nodeinPath(node->left,target-node->val); res+=nodeinPath(node->right,target-node->val); return res; } public: int pathSum(TreeNode* root, int targetSum) { if(root==NULL) return 0; int res=0; res+=nodeinPath(root,targetSum);//以自己为根 //改变传入的根,主函数递归 res+=pathSum(root->left,targetSum); res+=pathSum(root->right,targetSum); return res; } };
nodeinPath():
负责找值,每次以传入结点为根,递归的找:结点值==目标-已遍历结点和
- if(node->val == num) res += 1;--------找到的依据
- res +=nodeinPath(node->left , num - node->val);---递归减值
- res += nodeinPath(node->right , num - node->val);----递归减值
pathSum():
负责改变根,因为路径可以从孩子开始。
- findPath(root, sum) ;-----以自己为根
- pathSum(root->left , sum) + pathSum(root->right , sum);----改变根,递归主函数
二: 回溯
先复习vector遍历
注意:
- string的初始化:vector<string> 变量名{列表}
- 拷贝与赋值:=或者()
- auto与类型::iterator一个意思
树形问题
一些边界:
- 字符串的合法性
- 空字符串的处理---直接返回
- 多个解的顺序----这里无要求
class Solution { private: /*两个私有变量----数字代表值以及返回结果*/ const string letterMap[10]={ " ",//0 "",//1 "abc",//2 "def",//3 "ghi",//4 "jkl",//5 "mno",//6 "pqrs",//7 "tuv",//8 "wxyz"//9 }; vector<string> res; /*递归调用函数,完成函数功能*/ void findOneLetter(const string& digits,int index,const string& s){ //该函数,每次针对digits[index]处的字符的所有可能记录到s //s保存的是:digits[0...index-1]的所有结果 if(index==digits.size()){ //遍历结束,存储结果 res.push_back(s); return; } char c=digits[index]; string numToString=letterMap[c-'0'];//把char字符转换成数组下标int for(int i=0;i<numToString.size();i++){ findOneLetter(digits,index+1,s+numToString[i]); } } public: vector<string> letterCombinations(string digits) { if(digits=="") return res; findOneLetter(digits,0,""); return res; } };
findOneLetter(const string& digits,int index,string s)
几点注意:
char转换成int,直接字符减去48或者‘0’
string numToString=letterMap[digits[index]-'0'];
for(int i=0;i<numToString.size();i++)
findOneLetter(digits,index+1,s+numToString[i]);
一次循环递归便找到了所有可能
回溯法本质就是一个暴力解法
三:利用回溯法解决排列问题
class Solution { /*回溯过程: 全排列(数组)=从数组取出一个元素+全排列(不包含该元素的数组)*/ private: vector<vector<int>> res; vector<bool> used; void genPermute(const vector<int>& nums,int index,vector<int>& p){ if(index==nums.size()){ res.push_back(p); return; } //核心---递归逻辑 for(int i=0;i<nums.size();i++){ //判断该元素是否已经在p中 if(!used[i]){ //把nums[i]添加到p中 p.push_back(nums[i]); used[i]=true; //递归 genPermute(nums,index+1,p); //回溯 p.pop_back(); used[i]=false; } } } public: vector<vector<int>> permute(vector<int>& nums) { if(nums.size()==0) return res; used=vector<bool>(nums.size(),false); vector<int> p; genPermute(nums,0,p); return res; } };
#include<iostream> #include<vector> #include<string> using namespace std; /* 解题思路:全排列(数组)=随便选出一个元素+全排列(数组-选出的元素) 注意: 每次递归结束后可得到一个符合要求的结果,然后不断回溯得到所有结果 */ class Solution { /*全排列(nums)=随便选一个数a+全排列(nums没有a)*/ private: //私有变量:res为返回结果,visited表示元素是否访问 vector<vector<int>> res; vector<bool> visited; //递归求解 void getPermute(vector<int>& nums,int index,vector<int>& m){ //index:记录当前处理的第几个元素,是递归结束的标志 //m记录一个可能的结果 if(index==nums.size()){ //记录结果m+return res.push_back(m); return; } for(int i=0;i<nums.size();i++){ //保证了长度 if(!visited[i]){//没访问 /*当前元素加入m中,标记已访问*/ m.push_back(nums[i]); visited[i]=true; /*递归其他可能,满足m长度*/ getPermute(nums,index+1,m); /*递归结果,回溯,回溯要记得还原标记*/ m.pop_back(); visited[i]=false; } } return; } public: vector<vector<int>> permute(vector<int>& nums) { if(nums.size()==0) return res; //初始化变量 visited=vector<bool>(nums.size(),false); vector<int> p;//生成一个可能的排列,初始化不存在 getPermute(nums,0,p); return res; } }; int main(){ vector<int> aa={1,2,3}; vector<vector<int>> bb=Solution().permute(aa); for(int i=0;i<bb.size();i++){ cout<<"["; for(int j=0;j<bb[i].size();j++){ cout<<bb[i][j]<<","; } cout<<"] "; } return 0; }
看程序输出语句:
注意----如果在回退过程中,可选的结果已经执行了,递归或回溯有记忆。
其实index这个变量可以不引入
class Solution {
private:
vector<vector<int>> res;//记录返回结果
vector<bool> visited;//标记是否访问
//递归函数
void genPermute(vector<int>& nums,vector<int>& p){
if(p.size()==nums.size()){
res.push_back(p);
return;
}
for(int i=0;i<nums.size();i++){
if(!visited[i]){
p.push_back(nums[i]);
visited[i]=true;
genPermute(nums,p);
p.pop_back();
visited[i]=false;
}
}
return;
}
public:
vector<vector<int>> permute(vector<int>& nums) {
if(nums.size()==0) return res;
//对私有变量初始化
visited=vector<bool>(nums.size(),false);
vector<int> p;//记录单个正确结果
genPermute(nums,p);
return res;
}
};