目的
最近在刷leetcode二叉树时,发现回溯算法在二叉树中还是比较常见的,也遇到了一些问题,目前是有一个较为清晰的认识,当然可能也存在错误,总结下来,以便日后查看!!
问题
以两个两叉树题目为主展开
二叉树的所有路径
简单的说,就是将二叉树的所有路径输出。
代码呈现:
class Solution {
public:
void dfs(TreeNode* node, string path, vector<string>& result){
if (node != nullptr) {
path += to_string(node->val);
if (!node->left && !node->right) result.push_back(path);
path += "->";
dfs(node->left, path, result);
dfs(node->right, path, result);
}
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
dfs(root, "", result);
return result;
}
};
里面使用了回溯的思想,主要在path += "->"
,怎么体现呢?
想想如果下一个节点不满足是叶子节点,当它运行结束时,将会返回上一个节点对吧,由于path是值传递哦,所以将直接返回到当前的path,不用我们手动去pop掉path中的内容。
还有疑问?我猜想就是这确定是回溯吗?回溯不得删除吗?
其实这是一种简化了的版本,如果硬说是不是,可能是也可能不是,但我们可以说如果通过引用的方式传递path就必须手动释放path中的当前节点数据,否则你会多保存的。
能不能给出当前节点引用的回溯写法?
不能,如果我们使用的是vector存储每个字符串,最后再合成path是可以的,如果直接想在上面代码修改string& path变成引用方式是不可以的,原因主要是string字符串每次删除的只是最后一个字符,就是string.pop_back(),你只是删除了最后一个字符,对于整个字符串来说,你没有达到回退的效果,并且不同的字符串长度不一样,你每次回退的字符串数量也很难把握,还必须要找到字符串的长度,然后设置一个循环回退!想想都头疼,这里我也给出vector这种方式回溯的写法,大家一看便懂
class Solution {
private:
void traversal(TreeNode* cur, vector<int>& path, vector<string>& result) {
path.push_back(cur->val);
// 这才到了叶子节点
if (cur->left == NULL && cur->right == NULL) {
string sPath;
for (int i = 0; i < path.size() - 1; i++) {
sPath += to_string(path[i]);
sPath += "->";
}
sPath += to_string(path[path.size() - 1]);
result.push_back(sPath);
return;
}
if (cur->left) {
traversal(cur->left, path, result);
path.pop_back(); // 回溯
}
if (cur->right) {
traversal(cur->right, path, result);
path.pop_back(); // 回溯
}
}
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
vector<int> path;
if (root == NULL) return result;
traversal(root, path, result);
return result;
}
};
这里将的例子主要是为了告诫大家,string回退注意的问题,大家应该小心字符串真正回溯方法的实现,而使用值传递是可以避免的,当然也增加了代码实现的内容重复性。如果使用的是vector类型的向量是完全可以避免的!
最后,也总结一下我遇到的迭代法
- 强调一下,所有的递归是可以通过迭代来实现的,只是没有递归那么简单而已!
- 迭代有 创建双stack/queue方法,一个正常,一个非正常。
- 迭代有 创建stack<pair<TreeNode*, int>> 树对形式,来保证数据的传递!