前言
二叉树篇【257. 二叉树的所有路径】
回溯体现+1.
一、题目阅读
给你一个二叉树的根节点 root ,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
示例 1:
输入:root = [1,2,3,null,5]
输出:["1->2->5","1->3"]
示例 2:
输入:root = [1]
输出:["1"]
提示:
树中节点的数目在范围 [1, 100] 内
-100 <= Node.val <= 100
二、尝试实现
思路
(1)虽然结果return 类型是vector< string >,但是先用vector< int >搜集到所有路径之后,再整理成string,而且带有“->”符号。
(2)如果搜集每一条路径呢?还是要有遍历顺序。选择后序遍历(错误的),来看下为什么?(包括参考里面直接告诉用前序+递归+回溯,只说中序和后序错了。没说怎么错了。)
-
用vector<vector>二维数组,放每一条路径,最后整理成string就行(定在主函数中整理)。
-
对每一条路径,用递归实现。所以下面按步确定函数。
- 递归的返回值:返回值void。
- 递归的参数:需要节点TreeNode* cur,需要放路径的数组:vector<vector>& path。
- 递归终止条件:if(!cur->left && !cur->right) 叶子节点的时候。递归终止逻辑:遇到叶子节点时候,path要新建一个数组,把叶子结点放进去。打算把路径倒着放入数组中,比如“1->2->5”,放到数组中[5,2,1]。
-
后序遍历逻辑:
- 先if(!cur->left) traversal(cur->left)。遍历左子树;
- 再右子树不为空,添加右子树。
- 回到中间节点,把中间节点放到当前path的所有数组中。(bug,有的可以成功,有的不行。如下:)
(4)所以是中间节点添加位置不对,不应该在左右遍历结束后,对所有数组都加入元素。如果想左边结束,加入一次中间元素;右边遍历结束,加入一次中间元素。那么,不好控制左边遍历结束有几条路径,右边遍历多了几条路径。(卡住……)
(5)所以,后序遍历不行。
三、参考学习
1.学习内容
- 遍历方式:前序遍历(中左右);使用方法:递归+回溯。
- 回溯解释——目前接触到的回溯有:
- 层序遍历递归实现:对当前节点先递归左子树,同时传递深度depth+1;当回归后,depth减回当前深度;再递归右子树,同时传递深度depth+1.记录 三十七的补充最后提到。
- 记录四十三中最小深度递归法的代码实现 递归左子树时传递深度depth+1。当回归后,再遍历右子树,中间depth相当于先减回当前节点的深度,再+1传递给右子树。
- 所以,回溯是在递归的“归”出现减少、回退的操作。(浅显先理解)
3.那么找所有路径的时候:遇到叶子节点,说明这是一条路径。再找第二条路径,从叶子节点return,后面的逻辑要退出刚才加入的节点,这就是“减少、回退”,就是回溯。
所以:上图就是整个思路。包含递归:往下传递,并回归;回溯:回归之后,把path中元素减少。
代码实现1【递归+回溯】
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void traversal(TreeNode* cur,vector<int>& path,vector<string>& result){
path.push_back(cur->val);
//终止条件,叶子节点。整理path放到result中
if(!cur->left && !cur->right){
string p;
for(int i = 0;i < path.size();i++){
p += to_string(path[i]);
if(i != path.size()-1){
p += "->";
}
}
result.push_back(p);
return;
}
//先左后右,前序遍历。
if(cur->left) {
traversal(cur->left,path,result);
//从叶子节点return后,把加入的元素pop
path.pop_back();
}
if(cur->right) {
traversal(cur->right,path,result);
path.pop_back();
}
return;
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;//定义返回值
vector<int> path;//路径存放,每一个路径都重复用这个容器,回溯的对象也是它
traversal(root,path,result);//题目说root至少有一个节点
return result;
}
};
重点:
- 回溯:两个path.pop_back();就是对加入path的节点回归时,pop弹出。回溯和递归是一一对应的,有一个递归,就要有一个回溯。
2.精简版本
- 参考链接中提到一个精简版本,区别:
- 记录一条路径的path不用int类型的容器,也不在叶子节点处理时把int转换成string放到result。这两处替换成:
- 用string类型,边遍历节点,边整理“->”格式。
代码实现2【精简代码】
见注释
可以从【专栏】.记录 三十七的补充和记录四十三中最小深度递归法的代码实现中depth传递找到同样的用法。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void traversal(TreeNode* cur,string path,vector<string>& result){
//把当前节点的数值加到path上,“->”箭头在有子树的时候才添加传递。
path += to_string(cur->val);
//终止条件,叶子节点。把path直接放到result,没有整理的逻辑
if(!cur->left && !cur->right){
result.push_back(path);//result用的是引用,所以修改作用到result本身。
return;
}
//左子树
if(cur->left) traversal(cur->left,path + "->",result);
//第二个参数path不加引用,所以进入到左子树中path用的是副本,这里的path没变。
//也就是说,下一层的修改不影响当前这一层。
if(cur->right) traversal(cur->right,path + "->",result);
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
string path;
traversal(root,path,result);
return result;
}
};
3.迭代法
迭代思路
- 遍历顺序:前序(中左右)。需要结合前序的迭代实现:有一个栈,用来前序遍历;
- 记录路径:另外一个栈记录每一条路径,类型是string。如果遇到叶子节点,就把取出的这条路径放到result中。路径的栈变化状态有:
代码实现【迭代法】
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
stack<TreeNode*> st;//放遍历顺序的栈
stack<string> path;//放路径的栈
st.push(root);
path.push(to_string(root->val));
while(!st.empty()){
TreeNode* cur = st.top();st.pop();//前序遍历
string p = path.top();path.pop();//取出节点路径
if(!cur->left && !cur->right){//是叶子节点
result.push_back(p);
continue;
}
if(cur->right){//前序遍历先放右节点
st.push(cur->right);
//再把这个节点加到路径中
path.push(p + "->" + to_string(cur->right->val));
}
if(cur->left){
st.push(cur->left);
//再把这个节点加到路径中
path.push(p + "->" + to_string(cur->left->val));
}
}
return result;
}
};
总结
(欢迎指正,转载标明出处)