目录
一:左叶子之和
注意审题:求的是左叶子(左孩子的叶子结点)
思路一:
int sumOfLeftLeaves(TreeNode* root) { if(root==NULL) return 0; int res=0; if(root->left&&!root->left->right&&!root->left->left) res+=root->left->val; res+=sumOfLeftLeaves(root->left)+sumOfLeftLeaves(root->right); return res; }
- 根节点为空,直接return 0;定义变量res计算左孩子值
- 定义接收结果的条件----左孩子+叶子(左孩子的左右孩子均为空)
- 递归相加---传入左右孩子
- 别忘了return res
法二:
int sumOfLeftLeaves(TreeNode* root) { if(root==NULL) return 0; //遍历 int res=0; //左子树 if(root->left){ if(!root->left->left&&!root->left->right) res+=root->left->val;//左右孩子为空返回该结点值 else res+=sumOfLeftLeaves(root->left); } //右子树 if(root->right){ res+=sumOfLeftLeaves(root->right); } return res; }
深度遍历------代码符合dfs的感觉-----前序遍历
- 左子树-----判断左孩子是否为叶子,递归左孩子
- 右子树-----把该节点传入递归
法三
void dfs(TreeNode* root,bool isLeft,int &res){ if(root==NULL) return ; if(!root->left&&!root->right&&isLeft){ res+=root->val; } dfs(root->left,true,res); dfs(root->right,false,res); } int sumOfLeftLeaves(TreeNode* root) { int res=0; dfs(root,false,res); return res; }
这里更体现了“深度优先遍历”,传入所求和该节点自身是不是左孩子去判断。
void dfs(TreeNode* root,bool isLeft,int *res){ if(root==NULL) return ; if(!root->left&&!root->right&&isLeft){ *res+=root->val; } dfs(root->left,true,res); dfs(root->right,false,res); } int sumOfLeftLeaves(TreeNode* root) { int res=0; dfs(root,false,&res); return res; }
或者传入指针:
*res+=root->val; dfs(root,false,&res);
二:求叶子到根节点的最短路径
分析:
这道题思路不难,注意情况的分析,容易出错。
首先:
如果该节点有左子树和右子树,那么最短路径是左右子树最小值
如果该节点只有单子树,那么路径一定是该单子树的最短路径
法一:
int minDepth(TreeNode* root) { if(root==NULL) return 0; if(!root->left&&!root->right) return 1; //递归调用 int leftDeep=minDepth(root->left); int rightDeep=minDepth(root->right); //情况1---只有左子树或右子树 if(root->left&&root->right) return 1+min(leftDeep,rightDeep); //情况2---左右子树都有 return 1+leftDeep+rightDeep; }
难点在于情况的包含:根节点为空--return 0,根节点是叶子结点--return 1
如果有左右子树-----可以定义两个变量计算路径
情况:左右子树都存在,左右子树只存在一颗,这时候最好先判断都存在。
法二--广度优先遍历
int minDepth(TreeNode* root) { //广度优先遍历 if(root==NULL) return 0; queue<TreeNode*> que; que.push(root); int deep=1; while(!que.empty()){ int size=que.size(); for(int i=0;i<size;i++){ TreeNode* tmp=que.front(); que.pop(); if(!tmp->left&&!tmp->right) return deep; if(tmp->left) que.push(tmp->left); if(tmp->right) que.push(tmp->right); }//for deep++; } return deep; }
需要记录每次的队列元素,以判断当前所在层数
必须定义size变量,否则发生覆盖
int maxDepth(TreeNode* root) { //广度优先遍历 if(root==NULL) return 0; queue<TreeNode*> que; que.push(root); int deep=0; while(!que.empty()){ int size=que.size(); for(int i=0;i<size;i++){ TreeNode* tmp=que.front(); que.pop(); if(tmp->left) que.push(tmp->left); if(tmp->right) que.push(tmp->right); } deep++; } return deep; }
稍微一改就是求最大深度
- 把deep定义为0
- 去掉判断没有左右子树的语句
三:二叉树所有路径
法一:
vector<string> binaryTreePaths(TreeNode* root) { vector<string> res; if(root==NULL) return res; if(!root->left&&!root->right) {//叶子结点返回 res.push_back(to_string(root->val));} /*把左孩子路径并入结点*/ vector<string> leftPath=binaryTreePaths(root->left); for(int i=0;i<leftPath.size();i++) res.push_back(to_string(root->val)+"->"+leftPath[i]); /*把右孩子路径并入结点*/ vector<string> rightPath=binaryTreePaths(root->right); for(int i=0;i<rightPath.size();i++) res.push_back(to_string(root->val)+"->"+rightPath[i]); return res; }
to_string(整形):整形->字符串形
法二:
class Solution { private: vector<string> res;//定义全局变量,用于返回 void dfs(TreeNode* root,string s){//深度优先遍历 //递归结束 if(!root->left&&!root->right) res.push_back(s); //左路径 if(root->left){ dfs(root->left,s+"->"+to_string(root->left->val)); } //右路径 if(root->right){ dfs(root->right,s+"->"+to_string(root->right->val)); } } public: vector<string> binaryTreePaths(TreeNode* root) { if(root==NULL) return res; dfs(root,to_string(root->val)); return res; } };
很好的利用了深度优先遍历
- 把void dfs()和全局变量vectot<string> 定义成私有函数
- dfs需要两个参数------当前结点指针以及存储的字符串值
回溯的知识
class Solution { private: vector<string> res; void findPathBack(TreeNode* cur,vector<int>& node){ /*把非空结点的值记录下来*/ if(cur) node.push_back(cur->val); /*递归结束:叶子结点*/ if(!cur->left&&!cur->right){ string s; //难点:结点1->结点2->...->结点n //把“前面的结点和“->”视为整体”,最后并上最后一个结点 for(int i=0;i<node.size()-1;i++){ s+=to_string(node[i])+"->"; } s+=to_string(node[node.size()-1]);//int->string //并入结果 res.push_back(s); } //递归+回溯 if(cur->left){ findPathBack(cur->left,node); node.pop_back();//回溯,去掉该节点 } if(cur->right){ findPathBack(cur->right,node); node.pop_back();//回溯,去掉该节点 } } public: vector<string> binaryTreePaths(TreeNode* root) { if(root==NULL) return res; vector<int> node; findPathBack(root,node); return res; } };
如果借用,结点+“->”的形式,那么就要存在对结点回溯的语句
注意传参为了改变,加上&
也可以不定义私有变量,传三个参数
class Solution { private: void traversal(TreeNode* cur, vector<int>& valNode, vector<string>& res) { //cur---当前结点;val---存放结点值;res---记录返回路径 if(cur) valNode.push_back(cur->val); //达到叶子结点后 if(!cur->left&&!cur->right){ string s; //拼凑出:结点->。。。。->结点的形式 for(int i=0;i<valNode.size()-1;i++){ s+=to_string(valNode[i])+"->"; } s+=to_string(valNode[valNode.size()-1]); //返回给res res.push_back(s); /*递归结束----return*/ return; } if(cur->left){ traversal(cur->left,valNode,res); valNode.pop_back(); } if(cur->right){ traversal(cur->right,valNode,res); valNode.pop_back(); } } public: vector<string> binaryTreePaths(TreeNode* root) { vector<int> valNode; vector<string> res; if(root==NULL) return res; traversal(root,valNode,res); return res; } };
代码更改
class Solution { private: vector<string> res; void findPathBack(TreeNode* cur,string s){ /*把非空结点的值记录下来*/ if(cur) s+=to_string(cur->val); /*递归结束:叶子结点*/ if(!cur->left&&!cur->right){ res.push_back(s); return; } //递归 if(cur->left){ findPathBack(cur->left,s+"->"); //node.pop_back(); 不回溯 } if(cur->right){ findPathBack(cur->right,s+"->"); //node.pop_back(); 不回溯 } } public: vector<string> binaryTreePaths(TreeNode* root) { if(root==NULL) return res; string s; findPathBack(root,s); return res; } };
定义的是
string path
,每次都是复制赋值,不用使用引用,否则就无法做到回溯的效果。回溯就隐藏在traversal(cur->left, path + "->", result);中的 path + "->"。 每次函数调用完,path依然是没有加上"->" 的,这就是回溯了。
四:任意起点和任意终点的路径和
法一:双递归
首先要审题:
- 结点包含负数(比如“+8”,“-8”也会算上路径)
- 路径不要求起点和终点,只要求必须自上而下
思想:如果传入结点是路径起点,那么只要递归找到终点即可。
然后主算法负责更改结点起点。
总结,设计一个函数计算传入结点是路径起点符合数值的路径。主程序把所有结点都过一遍即可
代码
class Solution { private: int nodeIsStart(TreeNode* node,long sum){ //该函数把传入的node视为路径起点 //递归查找符合sum值得序列 if(node==NULL) return 0;//结点为空不存在 int res=0; if(node->val==sum) res+=1;//因为有负数存在,不return //递归查找,去掉node结点值路径 res+=nodeIsStart(node->left,sum-node->val)+nodeIsStart(node->right,sum-node->val); return res; } public: int pathSum(TreeNode* root, int targetSum) { if(root==NULL) return 0; int res=nodeIsStart(root,targetSum); //递归调用,该点传入结点,但是targetSum不变 res+=pathSum(root->left,targetSum)+pathSum(root->right,targetSum); return res; } };
res+=nodeIsStart(node->left,sum-node->val);
res+=nodeIsStart(node->right,sum-node->val);
class Solution { private: int dfs(TreeNode* node,long sum){ if(node==NULL) return 0; int res=0; res+=(node->val==sum)?1:0; res+=dfs(node->left,sum-node->val)+dfs(node->right,sum-node->val); return res; } public: int pathSum(TreeNode* root, int targetSum) { if(root==NULL) return 0; return dfs(root,targetSum)+pathSum(root->left,targetSum)+pathSum(root->right,targetSum); } };
简化
五:在bst上求两个结点的最近公共祖先
class Solution { public: TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { if(root==NULL) return NULL; if(p->val<root->val && q->val<root->val){ return lowestCommonAncestor(root->left,p,q); } if(p->val>root->val && q->val>root->val){ return lowestCommonAncestor(root->right,p,q); } //p,q在两个分支 return root; } };
很简单,p,q在树的两端一定只能是根节点,p,q在一颗子树则不停递归