diff --git a/README.md b/README.md index f039e36d..a975300e 100644 --- a/README.md +++ b/README.md @@ -284,6 +284,7 @@ |[0486.预测赢家](https://github.com/youngyangyang04/leetcode/blob/master/problems/0486.预测赢家.md) |动态规划 |中等| **递归** **记忆递归** **动态规划**| |[0491.递增子序列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0491.递增子序列.md) |深度优先搜索 |中等|**深度优先搜索/回溯算法**| |[0501.二叉搜索树中的众数](https://github.com/youngyangyang04/leetcode/blob/master/problems/0501.二叉搜索树中的众数.md) |二叉树 |简单|**递归/中序遍历**| +|[0513.找树左下角的值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0513.找树左下角的值.md) |二叉树 |中等|**递归** **迭代**| |[0515.在每个树行中找最大值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0515.在每个树行中找最大值.md) |二叉树 |简单|**广度优先搜索/队列**| |[0538.把二叉搜索树转换为累加树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0538.把二叉搜索树转换为累加树.md) |二叉树 |简单|**递归** **迭代**| |[0541.反转字符串II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0541.反转字符串II.md) |字符串 |简单| **模拟**| diff --git a/pics/100.相同的树.png b/pics/100.相同的树.png new file mode 100644 index 00000000..88da7600 Binary files /dev/null and b/pics/100.相同的树.png differ diff --git a/pics/110.平衡二叉树2.png b/pics/110.平衡二叉树2.png new file mode 100644 index 00000000..53d46be6 Binary files /dev/null and b/pics/110.平衡二叉树2.png differ diff --git a/pics/257.二叉树的所有路径.png b/pics/257.二叉树的所有路径.png index f46fc31a..0a0683e0 100644 Binary files a/pics/257.二叉树的所有路径.png and b/pics/257.二叉树的所有路径.png differ diff --git a/pics/257.二叉树的所有路径1.png b/pics/257.二叉树的所有路径1.png new file mode 100644 index 00000000..97452f51 Binary files /dev/null and b/pics/257.二叉树的所有路径1.png differ diff --git a/problems/0100.相同的树.md b/problems/0100.相同的树.md index 115ae411..4d9bb1e3 100644 --- a/problems/0100.相同的树.md +++ b/problems/0100.相同的树.md @@ -1,9 +1,115 @@ ## 题目地址 https://leetcode-cn.com/problems/same-tree/ -## 思路 +(没写完) -这道题目和101 基本是一样的 +# 100. 相同的树 + +给定两个二叉树,编写一个函数来检验它们是否相同。 + +如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。 + + + +# 思路 + +在[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)中,我们讲到对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了**其实我们要比较的是两个树(这两个树是根节点的左右子树)**,所以在递归遍历的过程中,也是要同时遍历两棵树。 + +理解这一本质之后,就会发现,求二叉树是否对称,和求二叉树是否相同几乎是同一道题目。 + +**如果没有读过[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)这一篇,请认真读完再做这道题,就会有感觉了。** + +递归三部曲中: + +1. 确定递归函数的参数和返回值 + +我们要比较的是两个树是否是相互相同的,参数也就是两个树的根节点。 + +返回值自然是bool类型。 + +代码如下: +``` +bool compare(TreeNode* tree1, TreeNode* tree2) +``` + +分析过程同[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)。 + +2. 确定终止条件 + +**要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。** + +节点为空的情况有: + +* tree1为空,tree2不为空,不对称,return false +* tree1不为空,tree2为空,不对称 return false +* tree1,tree2都为空,对称,返回true + +此时已经排除掉了节点为空的情况,那么剩下的就是tree1和tree2不为空的时候: + +* tree1、tree2都不为空,比较节点数值,不相同就return false + +此时tree1、tree2节点不为空,且数值也不相同的情况我们也处理了。 + +代码如下: +``` +if (tree1 == NULL && tree2 != NULL) return false; +else if (tree1 != NULL && tree2 == NULL) return false; +else if (tree1 == NULL && tree2 == NULL) return true; +else if (tree1->val != tree2->val) return false; // 注意这里我没有使用else +``` + +分析过程同[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg) + +3. 确定单层递归的逻辑 + +* 比较二叉树是否相同 :传入的是tree1的左孩子,tree2的右孩子。 +* 如果左右都相同就返回true ,有一侧不相同就返回false 。 + +代码如下: + +``` +bool outside = compare(tree1->left, tree2->right); // 左子树:左、 右子树:左 +bool inside = compare(tree1->right, tree2->left); // 左子树:右、 右子树:右 +bool isSame = outside && inside; // 左子树:中、 右子树:中(逻辑处理) +return isSame; +``` +-------------------------------------------------------------- 写到这 +最后递归的C++整体代码如下: + +``` +class Solution { +public: + bool compare(TreeNode* left, TreeNode* right) { + // 首先排除空节点的情况 + if (left == NULL && right != NULL) return false; + else if (left != NULL && right == NULL) return false; + else if (left == NULL && right == NULL) return true; + // 排除了空节点,再排除数值不相同的情况 + else if (left->val != right->val) return false; + + // 此时就是:左右节点都不为空,且数值相同的情况 + // 此时才做递归,做下一层的判断 + bool outside = compare(left->left, right->right); // 左子树:左、 右子树:右 + bool inside = compare(left->right, right->left); // 左子树:右、 右子树:左 + bool isSame = outside && inside; // 左子树:中、 右子树:中 (逻辑处理) + return isSame; + + } + bool isSymmetric(TreeNode* root) { + if (root == NULL) return true; + return compare(root->left, root->right); + } +}; +``` + +**我给出的代码并不简洁,但是把每一步判断的逻辑都清楚的描绘出来了。** + +如果上来就看网上各种简洁的代码,看起来真的很简单,但是很多逻辑都掩盖掉了,而题解可能也没有把掩盖掉的逻辑说清楚。 + +**盲目的照着抄,结果就是:发现这是一道“简单题”,稀里糊涂的就过了,但是真正的每一步判断逻辑未必想到清楚。** + +当然我可以把如上代码整理如下: +这道题目本质上和[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)是一样,因为 ## 递归 @@ -42,7 +148,6 @@ public: if (!leftNode && !rightNode) { // continue; } - // if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) { return false; diff --git a/problems/0110.平衡二叉树.md b/problems/0110.平衡二叉树.md index a03815bd..09d5598a 100644 --- a/problems/0110.平衡二叉树.md +++ b/problems/0110.平衡二叉树.md @@ -1,6 +1,8 @@ ## 题目地址 https://leetcode-cn.com/problems/balanced-binary-tree/ +> 求高度还是求深度,你搞懂了不? + # 110.平衡二叉树 给定一个二叉树,判断它是否是高度平衡的二叉树。 @@ -17,30 +19,117 @@ https://leetcode-cn.com/problems/balanced-binary-tree/ 示例 2: -给定二叉树 [1,2,2,3,3,null,null,4,4 +给定二叉树 [1,2,2,3,3,null,null,4,4] 返回 false 。 -## 思路 +# 题外话 -分析一下这道题是不是和求深度很像,在[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中是求左右子树的最大深度,本题呢,是要求左右子树高度差绝对值不超过1。 +咋眼一看这道题目和[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)很像,其实有很大区别。 +这里强调一波概念: +* 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。 +* 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。 + +但leetcode中强调的深度和高度很明显是按照节点来计算的,如图: + + + +关于根节点的深度究竟是1 还是 0,不同的地方有不一样的标准,leetcode的题目中都是以节点为一度,即根节点深度是1。但维基百科上定义用边为一度,即根节点的深度是0,我们暂时以leetcode为准(毕竟要在这上面刷题)。 + +因为求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中) + +有的同学一定疑惑,为什么[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中求的是二叉树的最大深度,也用的是后序遍历。 + +**那是因为代码的逻辑其实是求的根节点的高度,而根节点的高度就是这颗树的最大深度,所以才可以使用后序遍历。** + +在[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中,如果真正求取二叉树的最大深度,代码应该写成如下:(前序遍历) + +``` +class Solution { +public: + int result; + void getDepth(TreeNode* node, int depth) { + result = depth > result ? depth : result; // 中 + + if (node->left == NULL && node->right == NULL) return ; + + if (node->left) { // 左 + depth++; // 深度+1 + getDepth(node->left, depth); + depth--; // 回溯,深度-1 + } + if (node->right) { // 右 + depth++; // 深度+1 + getDepth(node->right, depth); + depth--; // 回溯,深度-1 + } + return ; + } + int maxDepth(TreeNode* root) { + result = 0; + if (root == 0) return result; + getDepth(root, 1); + return result; + } +}; +``` + +**可以看出使用了前序(中左右)的遍历顺序,这才是真正求深度的逻辑!** + +注意以上代码是为了把细节体现出来,简化一下代码如下: + +``` +class Solution { +public: + int result; + void getDepth(TreeNode* node, int depth) { + result = depth > result ? depth : result; // 中 + if (node->left == NULL && node->right == NULL) return ; + if (node->left) { // 左 + getDepth(node->left, depth + 1); + } + if (node->right) { // 右 + getDepth(node->right, depth + 1); + } + return ; + } + int maxDepth(TreeNode* root) { + result = 0; + if (root == 0) return result; + getDepth(root, 1); + return result; + } +}; +``` + +# 本题思路 + +## 递归 + +此时大家应该明白了既然要求比较高度,必然是要后序遍历。 递归三步曲分析: 1. 明确递归函数的参数和返回值 -参数的话为传入的节点指针,就没有其他参数需要传递了,**返回值要注意,我们的返回值是要求传入节点为根节点树的深度**,否则如何标记左右子树是否差值大于1呢。 +参数的话为传入的节点指针,就没有其他参数需要传递了,返回值要返回传入节点为根节点树的深度。 -这里还要注意一点,如果当前传入节点为根节点的二叉树已经不是二叉平衡树了,返回高度还有意义么? 此时可以返回-1 来标记已经不符合平衡树的规则了。 +那么如何标记左右子树是否差值大于1呢。 + +如果当前传入节点为根节点的二叉树已经不是二叉平衡树了,还返回高度的话就没有意义了。 + +所以如果已经不是二叉平衡树了,可以返回-1 来标记已经不符合平衡树的规则了。 代码如下: + ``` -int depth(TreeNode* node) +// -1 表示已经不是平衡二叉树了,否则返回值是以该节点为根节点树的高度 +int getDepth(TreeNode* node) ``` 2. 明确终止条件 @@ -64,52 +153,193 @@ if (node == NULL) { 代码如下: ``` -int leftDepth = depth(node->left); -if (leftDepth == -1) return -1; -int rightDepth = depth(node->right); +int leftDepth = depth(node->left); // 左 +if (leftDepth == -1) return -1; +int rightDepth = depth(node->right); // 右 +if (rightDepth == -1) return -1; + +int result; +if (abs(leftDepth - rightDepth) > 1) { // 中 + result = -1; +} else { + result = 1 + max(leftDepth, rightDepth); // 以当前节点为根节点的最大高度 +} + +return result; +``` + +代码精简之后如下: + +``` +int leftDepth = getDepth(node->left); +if (leftDepth == -1) return -1; +int rightDepth = getDepth(node->right); if (rightDepth == -1) return -1; return abs(leftDepth - rightDepth) > 1 ? -1 : 1 + max(leftDepth, rightDepth); ``` -此时递归的函数就已经写出来了,这个递归的函数传入节点指针,返回以该节点为根节点的二叉树的高度,如果不是二叉平衡树了则返回-1。 +此时递归的函数就已经写出来了,这个递归的函数传入节点指针,返回以该节点为根节点的二叉树的高度,如果不是二叉平衡树,则返回-1。 -代码如下: +getDepth整体代码如下: ``` -int depth(TreeNode* node) { +int getDepth(TreeNode* node) { if (node == NULL) { return 0; } - int leftDepth = depth(node->left); + int leftDepth = getDepth(node->left); if (leftDepth == -1) return -1; - int rightDepth = depth(node->right); + int rightDepth = getDepth(node->right); if (rightDepth == -1) return -1; return abs(leftDepth - rightDepth) > 1 ? -1 : 1 + max(leftDepth, rightDepth); } ``` -整体代码如下: - -## C++代码 +最后本题整体递归代码如下: ``` class Solution { public: // 返回以该节点为根节点的二叉树的高度,如果不是二叉搜索树了则返回-1 - int depth(TreeNode* node) { + int getDepth(TreeNode* node) { if (node == NULL) { return 0; } - int leftDepth = depth(node->left); + int leftDepth = getDepth(node->left); if (leftDepth == -1) return -1; // 说明左子树已经不是二叉平衡树 - int rightDepth = depth(node->right); + int rightDepth = getDepth(node->right); if (rightDepth == -1) return -1; // 说明右子树已经不是二叉平衡树 return abs(leftDepth - rightDepth) > 1 ? -1 : 1 + max(leftDepth, rightDepth); } bool isBalanced(TreeNode* root) { - return depth(root) == -1 ? false : true; + return getDepth(root) == -1 ? false : true; } }; ``` +## 迭代 + +在[二叉树:看看这些树的最大深度](https://mp.weixin.qq.com/s/guKwV-gSNbA1CcbvkMtHBg)中我们可以使用层序遍历来求深度,但是就不能直接用层序遍历来求高度了,这就体现出求高度和求深度的不同。 + +本题的迭代方式可以先定义一个函数,专门用来求高度。 + +这个函数通过栈模拟的后序遍历找每一个节点的高度(其实是通过求传入节点为根节点的最大深度来求的高度) + +代码如下: + +``` +// cur节点的最大深度,就是cur的高度 +int getDepth(TreeNode* cur) { + stack st; + if (cur != NULL) st.push(cur); + int depth = 0; // 记录深度 + int result = 0; + while (!st.empty()) { + TreeNode* node = st.top(); + if (node != NULL) { + st.pop(); + st.push(node); // 中 + st.push(NULL); + depth++; + if (node->right) st.push(node->right); // 右 + if (node->left) st.push(node->left); // 左 + + } else { + st.pop(); + node = st.top(); + st.pop(); + depth--; + } + result = result > depth ? result : depth; + } + return result; +} +``` + +然后再用栈来模拟前序遍历,遍历每一个节点的时候,再去判断左右孩子的高度是否符合,代码如下: + +``` +bool isBalanced(TreeNode* root) { + stack st; + if (root == NULL) return true; + st.push(root); + while (!st.empty()) { + TreeNode* node = st.top(); // 中 + st.pop(); + if (abs(getDepth(node->left) - getDepth(node->right)) > 1) { // 判断左右孩子高度是否符合 + return false; + } + if (node->right) st.push(node->right); // 右(空节点不入栈) + if (node->left) st.push(node->left); // 左(空节点不入栈) + } + return true; +} +``` + +整体代码如下: + +``` +class Solution { +private: + int getDepth(TreeNode* cur) { + stack st; + if (cur != NULL) st.push(cur); + int depth = 0; // 记录深度 + int result = 0; + while (!st.empty()) { + TreeNode* node = st.top(); + if (node != NULL) { + st.pop(); + st.push(node); // 中 + st.push(NULL); + depth++; + if (node->right) st.push(node->right); // 右 + if (node->left) st.push(node->left); // 左 + + } else { + st.pop(); + node = st.top(); + st.pop(); + depth--; + } + result = result > depth ? result : depth; + } + return result; + } + +public: + bool isBalanced(TreeNode* root) { + stack st; + if (root == NULL) return true; + st.push(root); + while (!st.empty()) { + TreeNode* node = st.top(); // 中 + st.pop(); + if (abs(getDepth(node->left) - getDepth(node->right)) > 1) { + return false; + } + if (node->right) st.push(node->right); // 右(空节点不入栈) + if (node->left) st.push(node->left); // 左(空节点不入栈) + } + return true; + } +}; +``` + +当然此题用迭代法,其实效率很低,因为没有很好的模拟回溯的过程,所以迭代法有很多重复的计算。 + +虽然理论上所有的递归都可以用迭代来实现,但是有的场景难度可能比较大。 + +**例如:都知道回溯法其实就是递归,但是很少人用迭代的方式去实现回溯算法!** + +因为对于回溯算法已经是非常复杂的递归了,如果在用迭代的话,就是自己给自己找麻烦,效率也并不一定高。 + +# 总结 + +通过本题可以了解求二叉树深度 和 二叉树高度的差异,求深度适合用前序遍历,而求高度适合用后序遍历。 + +本题迭代法其实有点复杂,大家可以有一个思路,也不一定说非要写出来。 + +但是递归方式是一定要掌握的! + > 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。 diff --git a/problems/0257.二叉树的所有路径.md b/problems/0257.二叉树的所有路径.md index 0772444b..3fcbe67d 100644 --- a/problems/0257.二叉树的所有路径.md +++ b/problems/0257.二叉树的所有路径.md @@ -1,42 +1,34 @@ ## 题目地址 https://leetcode-cn.com/problems/binary-tree-paths/ +> 以为只用了递归,其实还用了回溯 -## 思路 +# 257. 二叉树的所有路径 -首先要知道遍历二叉树有两种遍历方式:二叉树深度优先遍历和二叉树广度优先遍历,那么每种遍历方式下还有不同的顺序。如下所示: -* 二叉树深度优先遍历 - * 前序遍历: [0144.二叉树的前序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md) - * 后序遍历: [0145.二叉树的后序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0145.二叉树的后序遍历.md) - * 中序遍历: [0094.二叉树的中序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0094.二叉树的中序遍历.md) -* 二叉树广度优先遍历 - * 层序遍历:[0102.二叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0102.二叉树的层序遍历.md) +给定一个二叉树,返回所有从根节点到叶子节点的路径。 -这道题目要打印出根节点到叶子节点的所有路径,很明显广度优先遍历不合适,那么深度优先遍历中,应该选哪一种循序来遍历呢? +说明: 叶子节点是指没有子节点的节点。 -**要打印路径,就要选前序遍历**,因为中序和后序遍历都不能打印出路径来。 +示例: + -一些同学可能代码都写出来,而且都提交通过了,却不知道自己用了哪一种遍历,以及那种顺序来遍历的。 +# 思路 -前序遍历如题: +这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。 + +在这道题目中将第一次涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一一个路径在进入另一个路径。 + +前序遍历以及回溯的过程如图: -确定了是前序遍历,那么就是中左右的顺序。前序遍历 框架如下: -``` -void traversal(TreeNode* cur, vector& vec) { - if (cur == NULL) return; - vec.push_back(cur->val); // 中 ,同时也是处理节点逻辑的地方 - traversal(cur->left, vec); // 左 - traversal(cur->right, vec); // 右 -} -``` +我们先使用递归的方式,来做前序遍历。**要知道递归和回溯就是一家的,本题也需要回溯。** -我们先使用递归的方式,来做前序遍历。那么要知道递归和回溯就是一家的,本题也需要回溯。 +## 递归 1. 递归函数函数参数以及返回值 -要传入跟节点,记录每一条路径的path,和存放结果集的result,这里递归不需要返回值,代码如下: +要传入根节点,记录每一条路径的path,和存放结果集的result,这里递归不需要返回值,代码如下: ``` void traversal(TreeNode* cur, vector& path, vector& result) @@ -44,7 +36,7 @@ void traversal(TreeNode* cur, vector& path, vector& result) 2. 确定递归终止条件 -在写递归的时候都习惯了这么写: +再写递归的时候都习惯了这么写: ``` if (cur == NULL) { @@ -63,29 +55,29 @@ if (cur->left == NULL && cur->right == NULL) { } ``` -为什么没有判断cur是否为空呢,下文在讲解单层递归逻辑的时候会提到。 +为什么没有判断cur是否为空呢,因为下面的逻辑可以控制空节点不入循环。 再来看一下终止处理的逻辑。 -这里使用vector 结构来记录路径,所以要把路径转为string格式,在把这个string 放进 result里。 +这里使用vector 结构path来记录路径,所以要把vector 结构的path转为string格式,在把这个string 放进 result里。 **那么为什么使用了vector 结构来记录路径呢?** 因为在下面处理单层递归逻辑的时候,要做回溯,使用vector方便来做回溯。 -那么有的同学问了,我看有些人的代码也没有回溯啊。 +可能有的同学问了,我看有些人的代码也没有回溯啊。 -其实是有的,只不过隐藏在 函数调用时的参数赋值里,下文我还会提到。 +**其实是有回溯的,只不过隐藏在函数调用时的参数赋值里**,下文我还会提到。 -这里我们先使用vector 结构来记录路径,那么终止处理逻辑如下: +这里我们先使用vector结构的path容器来记录路径,那么终止处理逻辑如下: ``` -if (cur->left == NULL && cur->right == NULL) { +if (cur->left == NULL && cur->right == NULL) { // 遇到叶子节点 string sPath; - for (int i = 0; i < path.size() - 1; i++) { + for (int i = 0; i < path.size() - 1; i++) { // 将path里记录的路径转为string格式 sPath += to_string(path[i]); sPath += "->"; } - sPath += to_string(path[path.size() - 1]); - result.push_back(sPath); + sPath += to_string(path[path.size() - 1]); // 记录最后一个节点(叶子节点) + result.push_back(sPath); // 收集一个路径 return; } ``` @@ -142,8 +134,6 @@ if (cur->right) { 那么本题整体代码如下: -## C++代码第一种写法 - ``` class Solution { private: @@ -181,23 +171,22 @@ public: } }; ``` +如上的C++代码充分体现了回溯。 -## C++代码第二种写法 - -接下来我介绍另一种写法,如下写法就是一个标准的前序遍历的过程。 +那么如上代码可以精简成如下代码: ``` class Solution { private: void traversal(TreeNode* cur, string path, vector& result) { - path += to_string(cur->val); + path += to_string(cur->val); // 中 if (cur->left == NULL && cur->right == NULL) { result.push_back(path); return; } - if (cur->left) traversal(cur->left, path + "->", result); - if (cur->right) traversal(cur->right, path + "->", result); + if (cur->left) traversal(cur->left, path + "->", result); // 左 + if (cur->right) traversal(cur->right, path + "->", result); // 右 } public: @@ -212,14 +201,64 @@ public: }; ``` -注意在函数定义的时候`void traversal(TreeNode* cur, string path, vector& result)` ,定义的是`string path`,说明每次都是复制赋值。 +如上代码精简了不少,也隐藏了不少东西。 -那么在如上代码中,**貌似没有看到回溯的逻辑,其实不然,回溯就隐藏在`traversal(cur->left, path + "->", result);`中的 `path + "->"`。** 每次函数调用完,path依然是没有+ 上"->" 的,这就是回溯了。 +注意在函数定义的时候`void traversal(TreeNode* cur, string path, vector& result)` ,定义的是`string path`,每次都是复制赋值,不用使用引用,否则就无法做到回溯的效果。 -**综合以上,第二种写法更简洁,但是把很多重要的点隐藏在了代码细节里,第一种写法虽然代码多一些,但是每一个处理逻辑都完整的展现了出来。** +那么在如上代码中,**貌似没有看到回溯的逻辑,其实不然,回溯就隐藏在`traversal(cur->left, path + "->", result);`中的 `path + "->"`。** 每次函数调用完,path依然是没有加上"->" 的,这就是回溯了。 -至于还有非递归的方式,我在这篇题解[彻底吃透前中后序递归法(递归三部曲)和迭代法(不统一写法与统一写法)](https://leetcode-cn.com/problems/binary-tree-preorder-traversal/solution/dai-ma-sui-xiang-lu-chi-tou-qian-zhong-hou-xu-de-d/) 已经彻底介绍过了,感兴趣的同学可以去看一看。 +**综合以上,第二种递归的代码虽然精简但把很多重要的点隐藏在了代码细节里,第一种递归写法虽然代码多一些,但是把每一个逻辑处理都完整的展现了出来了。** +## 迭代法 + +至于非递归的方式,我们可以依然可以使用前序遍历的迭代方式来模拟遍历路径的过程,对该迭代方式不了解的同学,可以看文章[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg)和[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)。 + +这里除了模拟递归需要一个栈,同时还需要一个栈来存放对应的遍历路径。 + +C++代码如下: + +``` +class Solution { +public: + vector binaryTreePaths(TreeNode* root) { + stack treeSt;// 保存树的遍历节点 + stack pathSt; // 保存遍历路径的节点 + vector result; // 保存最终路径集合 + if (root == NULL) return result; + treeSt.push(root); + pathSt.push(to_string(root->val)); + while (!treeSt.empty()) { + TreeNode* node = treeSt.top(); treeSt.pop(); // 取出节点 中 + string path = pathSt.top();pathSt.pop(); // 取出该节点对应的路径 + if (node->left == NULL && node->right == NULL) { // 遇到叶子节点 + result.push_back(path); + } + if (node->right) { // 右 + treeSt.push(node->right); + pathSt.push(path + "->" + to_string(node->right->val)); + } + if (node->left) { // 左 + treeSt.push(node->left); + pathSt.push(path + "->" + to_string(node->left->val)); + } + } + return result; + } +}; +``` +当然,使用java的同学,可以直接定义一个成员变量为object的栈`Stack stack = new Stack<>();`,这样就不用定义两个栈了,都放到一个栈里就可以了。 + +# 总结 + +**本文我们开始初步涉及到了回溯,很多同学过了这道题目,可能都不知道自己其实使用了回溯,回溯和递归都是相伴相生的。** + +我在第一版递归代码中,把递归与回溯的细节都充分的展现了出来,大家可以自己感受一下。 + +第二版递归代码对于初学者其实非常不友好,代码看上去简单,但是隐藏细节于无形。 + +最后我依然给出了迭代法。 + +对于本地充分了解递归与回溯的过程之后,有精力的同学可以在去实现迭代法。 diff --git a/problems/0513.找树左下角的值.md b/problems/0513.找树左下角的值.md new file mode 100644 index 00000000..1da53893 --- /dev/null +++ b/problems/0513.找树左下角的值.md @@ -0,0 +1,58 @@ + +## C++代码递归 + +``` +class Solution { +public: + int maxLen = INT_MIN; + int maxleftValue; + void traversal(TreeNode* root, int leftLen) { + if (root->left == NULL && root->right == NULL) { + if (leftLen > maxLen) { + maxleftValue = root->val; + maxLen = leftLen; + } + return; + } + if (root->left) { + leftLen++; + traversal(root->left, leftLen); + leftLen--; + } + if (root->right) { + leftLen++; + traversal(root->right, leftLen); + leftLen--; + } + return; + } + int findBottomLeftValue(TreeNode* root) { + traversal(root, 0); + return maxleftValue; + } +}; +``` + +## C++代码层序遍历 + +``` +class Solution { +public: + int findBottomLeftValue(TreeNode* root) { + queue que; + if (root != NULL) que.push(root); + int result = 0; + while (!que.empty()) { + int size = que.size(); + for (int i = 0; i < size; i++) { + TreeNode* node = que.front(); + que.pop(); + if (i == 0) result = node->val; + if (node->left) que.push(node->left); + if (node->right) que.push(node->right); + } + } + return result; + } +}; +``` diff --git a/problems/0572.另一个树的子树.md b/problems/0572.另一个树的子树.md index 90d9927a..fdac6177 100644 --- a/problems/0572.另一个树的子树.md +++ b/problems/0572.另一个树的子树.md @@ -23,3 +23,47 @@ public: } }; ``` + + +``` +class Solution { +public: + bool isSameTree(TreeNode* p, TreeNode* q) { + if (p == NULL && q == NULL) return true; + if (p == NULL || q == NULL) return false; + queue que; + que.push(p); // + que.push(q); // + while (!que.empty()) { // + TreeNode* leftNode = que.front(); que.pop(); + TreeNode* rightNode = que.front(); que.pop(); + if (!leftNode && !rightNode) { // + continue; + } + if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) { + return false; + } + que.push(leftNode->left); // + que.push(rightNode->left); // + que.push(leftNode->right); // + que.push(rightNode->right); // + } + return true; + } + + bool isSubtree(TreeNode* s, TreeNode* t) { + stack st; + vector result; + if (s == NULL) return false; + st.push(s); + while (!st.empty()) { + TreeNode* node = st.top(); // 中 + st.pop(); + if(isSameTree(node, t)) return true; + if (node->right) st.push(node->right); // 右(空节点不入栈) + if (node->left) st.push(node->left); // 左(空节点不入栈) + } + return false; + } +}; +```