diff --git a/README.md b/README.md index d481b3e9..e805d192 100644 --- a/README.md +++ b/README.md @@ -239,6 +239,8 @@ |[0110.平衡二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0110.平衡二叉树.md) |树 |简单|**递归**| |[0111.二叉树的最小深度](https://github.com/youngyangyang04/leetcode/blob/master/problems/0111.二叉树的最小深度.md) |树 |简单|**递归** **队列/BFS**| |[0112.路径总和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0112.路径总和.md) |树 |简单|**深度优先搜索/递归** **回溯** **栈**| +|[0116.填充每个节点的下一个右侧节点指针](https://github.com/youngyangyang04/leetcode/blob/master/problems/0116.填充每个节点的下一个右侧节点指针.md) |二叉树 |中等|**广度优先搜索**| +|[0117.填充每个节点的下一个右侧节点指针II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0117.填充每个节点的下一个右侧节点指针II.md) |二叉树 |中等|**广度优先搜索**| |[0131.分割回文串](https://github.com/youngyangyang04/leetcode/blob/master/problems/0131.分割回文串.md) |回溯 |中等|**回溯**| |[0142.环形链表II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0142.环形链表II.md) |链表 |中等|**快慢指针/双指针**| |[0144.二叉树的前序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0144.二叉树的前序遍历.md) |树 |中等|**递归** **迭代/栈**| @@ -257,6 +259,8 @@ |[0225.用队列实现栈](https://github.com/youngyangyang04/leetcode/blob/master/problems/0225.用队列实现栈.md) | 队列 |简单| **队列** | |[0226.翻转二叉树](https://github.com/youngyangyang04/leetcode/blob/master/problems/0226.翻转二叉树.md) |二叉树 |简单| **递归** **迭代**| |[0232.用栈实现队列](https://github.com/youngyangyang04/leetcode/blob/master/problems/0232.用栈实现队列.md) | 栈 |简单| **栈** | +|[0235.二叉搜索树的最近公共祖先](https://github.com/youngyangyang04/leetcode/blob/master/problems/0235.二叉搜索树的最近公共祖先.md) | 二叉搜索树 |简单| **递归** **迭代** | +|[0236.二叉树的最近公共祖先](https://github.com/youngyangyang04/leetcode/blob/master/problems/0236.二叉树的最近公共祖先.md) | 二叉树 |中等| **递归/回溯** | |[0237.删除链表中的节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0237.删除链表中的节点.md) |链表 |简单| **原链表移除** **添加虚拟节点** 递归| |[0239.滑动窗口最大值](https://github.com/youngyangyang04/leetcode/blob/master/problems/0239.滑动窗口最大值.md) |滑动窗口/队列 |困难| **单调队列**| |[0242.有效的字母异位词](https://github.com/youngyangyang04/leetcode/blob/master/problems/0242.有效的字母异位词.md) |哈希表 |简单| **哈希**| diff --git a/pics/101. 对称二叉树.png b/pics/101. 对称二叉树.png new file mode 100644 index 00000000..6485026d Binary files /dev/null and b/pics/101. 对称二叉树.png differ diff --git a/pics/101. 对称二叉树1.png b/pics/101. 对称二叉树1.png new file mode 100644 index 00000000..98286136 Binary files /dev/null and b/pics/101. 对称二叉树1.png differ diff --git a/pics/116.填充每个节点的下一个右侧节点指针.png b/pics/116.填充每个节点的下一个右侧节点指针.png new file mode 100644 index 00000000..0078731b Binary files /dev/null and b/pics/116.填充每个节点的下一个右侧节点指针.png differ diff --git a/problems/0101.对称二叉树.md b/problems/0101.对称二叉树.md index 55ca224b..b454aa7d 100644 --- a/problems/0101.对称二叉树.md +++ b/problems/0101.对称二叉树.md @@ -1,59 +1,136 @@ ## 题目地址 https://leetcode-cn.com/problems/symmetric-tree/ -## 思路 +> 又是一道“简单题” -这是考察二叉树基本操作的经典题目,递归的方式相对好理解一些,迭代法看大家清一色使用队列,其实使用栈也是可以的,只不过遍历的顺序不同而已,关键是要理解只要是对称比较就可以了,遍历的顺序无所谓的。 +# 101. 对称二叉树 +给定一个二叉树,检查它是否是镜像对称的。 -### 递归法 + -#### 递归三部曲 +# 思路 -* 确定递归函数的参数和返回值 -* 确定终止条件 -* 确定单层递归的逻辑 +**首先想清楚,判断对称二叉树要比较的是哪两个节点,要比较的可不是左右节点!** -##### 确定递归函数的参数和返回值 +对于二叉树是否对称,要比较的是根节点的左子树与右子树是不是相互翻转的,理解这一点就知道了**其实我们要比较的是两个树(这两个树是根节点的左右子树)**,所以在递归遍历的过程中,也是要同时遍历两棵树。 -判断左右孩子是否对称,所以传入的参数为左指针和右指针,那么就返回是否是对称的就可以了,所以返回值是布尔类型。 +那么如果比较呢? + +比较的是两个子树的里侧和外侧的元素是否相等。如图所示: + + + +那么遍历的顺序应该是什么样的呢? + +本题遍历只能是“后序遍历”,因为我们要通过递归函数的返回值来判断两个子树的内侧节点和外侧节点是否相等。 + +**正是因为要遍历两棵树而且要比较内侧和外侧节点,所以准确的来说是一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。** + +但都可以理解算是后序遍历,尽管已经不是严格上在一个树上进行遍历的后序遍历了。 + +其实后序也可以理解为是一种回溯,当然这是题外话,讲回溯的时候会重点讲的。 + +说到这大家可能感觉我有点啰嗦,哪有这么多道理,上来就干就完事了。别急,我说的这些在下面的代码讲解中都有身影。 + +那么我们先来看看递归法的代码应该怎么写。 + +## 递归法 + +### 递归三部曲 + +1. 确定递归函数的参数和返回值 + +因为我们要比较的是根节点的两个子树是否是相互翻转的,进而判断这个树是不是对称树,所以要比较的是两个树,参数自然也是左子树节点和右子树节点。 + +返回值自然是bool类型。 代码如下: ``` bool compare(TreeNode* left, TreeNode* right) ``` -##### 确定终止条件 +2. 确定终止条件 -* 左孩子为空,右孩子不为空,不对称,return false +要比较两个节点数值相不相同,首先要把两个节点为空的情况弄清楚!否则后面比较数值的时候就会操作空指针了。 + +节点为空的情况有:(**注意我们比较的其实不是左孩子和右孩子,所以如下我称之为左节点右节点**) + +* 左节点为空,右节点不为空,不对称,return false * 左不为空,右为空,不对称 return false * 左右都为空,对称,返回true + +此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空: + * 左右都不为空,比较节点数值,不相同就return false +此时左右节点不为空,且数值也不相同的情况我们也处理了。 + 代码如下: ``` 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; +else if (left == NULL && right == NULL) return true; +else if (left->val != right->val) return false; // 注意这里我没有使用else ``` -##### 确定单层递归的逻辑 +注意上面最后一种情况,我没有使用else,而是elseif, 因为我们把以上情况都排除之后,剩下的就是 左右节点都不为空,且数值相同的情况。 -* 比较二叉树外侧是否对称:传入的是左孩子的左指针,右孩子的右指针。 -* 比较内测是否对称,传入左孩子的右指针,右孩子的左指针。 +3. 确定单层递归的逻辑 + +此时才进入单层递归的逻辑,单层递归的逻辑就是处理 右节点都不为空,且数值相同的情况。 + + +* 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。 +* 比较内测是否对称,传入左节点的右孩子,右节点的左孩子。 * 如果左右都对称就返回true ,有一侧不对称就返回false 。 代码如下: ``` -bool outside = compare(left->left, right->right); -bool inside = compare(left->right, right->left); -return outside && inside; +bool outside = compare(left->left, right->right); // 左子树:左、 右子树:右 +bool inside = compare(left->right, right->left); // 左子树:右、 右子树:左 +bool isSame = outside && inside; // 左子树:中、 右子树:中(逻辑处理) +return isSame; ``` -这样递归的C++代码就写出来了,如下: +如上代码中,我们可以看出使用的遍历方式,左子树左右中,右子树右左中,所以我把这个遍历顺序也称之为“后序遍历”(尽管不是严格的后序遍历)。 +最后递归的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); + } +}; +``` + +**我给出的代码并不简洁,但是把每一步判断的逻辑都清楚的描绘出来了。** + +如果上来就看网上各种简洁的代码,看起来真的很简单,但是很多逻辑都掩盖掉了,而题解可能也没有把掩盖掉的逻辑说清楚。 + +**盲目的照着抄,结果就是:发现这是一道“简单题”,稀里糊涂的就过了,但是真正的每一步判断逻辑未必想到清楚。** + +当然我可以把如上代码整理如下: ``` class Solution { public: @@ -72,11 +149,24 @@ public: }; ``` -### 迭代法 +**这个代码就很简洁了,但隐藏了很多逻辑,条理不清晰,而且递归三部曲,在这里完全体现不出来。** -通过队列来判断二叉树内侧和外侧是否相等,如动画所示: +**所以建议大家做题的时候,一定要想清楚逻辑,每一步做什么。把道题目所有情况想到位,相应的代码写出来之后,再去追求简洁代码的效果。** - +## 迭代法 + +这道题目我们也可以使用迭代法,但要注意,这里的迭代法可不是前中后序的迭代写法,因为本题的本质是判断两个树是否是相互翻转的,其实已经不是所谓二叉树遍历的前中后序的关系了。 + +这里我们可以使用队列来比较两个树(根节点的左右子树)是否相互翻转,(**注意这不是层序遍历**) + +### 使用队列 + +通过队列来判断根节点的左子树和右子树的内侧和外侧是否相等,如动画所示: + + + + +如下的条件判断和递归的逻辑是一样的。 代码如下: @@ -86,90 +176,41 @@ public: bool isSymmetric(TreeNode* root) { if (root == NULL) return true; queue que; - que.push(root->left); - que.push(root->right); - while (!que.empty()) { - TreeNode* leftNode = que.front(); que.pop(); + que.push(root->left); // 将左子树头结点加入队列 + que.push(root->right); // 将右子树头结点加入队列 + while (!que.empty()) { // 接下来就要判断这这两个树是否相互翻转 + TreeNode* leftNode = que.front(); que.pop(); TreeNode* rightNode = que.front(); que.pop(); - if (!leftNode && !rightNode) { + if (!leftNode && !rightNode) { // 左节点为空、右节点为空,此时说明是对称的 continue; } - if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) { + + // 左右一个节点不为空,或者都不为空但数值不相同,返回false + if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) { return false; } - que.push(leftNode->left); - que.push(rightNode->right); - que.push(leftNode->right); - que.push(rightNode->left); + que.push(leftNode->left); // 加入左节点左孩子 + que.push(rightNode->right); // 加入右节点右孩子 + que.push(leftNode->right); // 加入左节点右孩子 + que.push(rightNode->left); // 加入右节点左孩子 } return true; } }; ``` -其实使用栈也是可以的,只要把队列原封不动的改成栈就可以了,我下面也给出了代码。 -## 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; - else return compare(left->left, right->right) && compare(left->right, right->left); - - } - bool isSymmetric(TreeNode* root) { - if (root == NULL) return true; - return compare(root->left, root->right); - } -}; -``` - -### 迭代 - -使用队列 +只要把队列原封不动的改成栈就可以了,我下面也给出了代码。 ``` class Solution { public: bool isSymmetric(TreeNode* root) { if (root == NULL) return true; - queue que; - que.push(root->left); - que.push(root->right); - 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->right); - que.push(leftNode->right); - que.push(rightNode->left); - } - return true; - } -}; - -``` - -使用栈 - -``` -class Solution { -public: - bool isSymmetric(TreeNode* root) { - if (root == NULL) return true; - stack st; + stack st; // 这里改成了栈 st.push(root->left); st.push(root->right); while (!st.empty()) { @@ -191,5 +232,15 @@ public: }; ``` +# 总结 + +这次我们又深度剖析了一道二叉树的“简单题”,大家会发现,真正的把题目搞清楚其实并不简单,leetcode上accept了和真正掌握了还是有距离的。 + +我们介绍了递归法和迭代法,递归依然通过递归三部曲来解决了这道题目,如果只看精简的代码根本看不出来递归三部曲是如果解题的。 + +在迭代法中我们使用了队列,需要注意的是这不是层序遍历,而且仅仅通过一个容器来成对的存放我们要比较的元素,知道这一本质之后就发现,用队列,用栈,甚至用数组,都是可以的。 + +如果已经做过这道题目的同学,读完文章可以再去看看这道题目,思考一下,会有不一样的发现! + > 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。 diff --git a/problems/0102.二叉树的层序遍历.md b/problems/0102.二叉树的层序遍历.md index 2c8cd97e..e00ec288 100644 --- a/problems/0102.二叉树的层序遍历.md +++ b/problems/0102.二叉树的层序遍历.md @@ -3,7 +3,18 @@ https://leetcode-cn.com/problems/binary-tree-level-order-traversal/ > 我要打十个! -看完这篇文章虽然不能打十个,但是可以迅速打六个!而且够快! +看完这篇文章虽然不能打十个,但是可以迅速打八个!而且够快! + +学会二叉树的层序遍历,可以一口气撸完leetcode上八道题目: + +* 102.二叉树的层序遍历 +* 107.二叉树的层次遍历II +* 199.二叉树的右视图 +* 637.二叉树的层平均值 +* 429.N叉树的前序遍历 +* 515.在每个树行中找最大值 +* 116. 填充每个节点的下一个右侧节点指针 +* 117.填充每个节点的下一个右侧节点指针II # 102.二叉树的层序遍历 @@ -35,7 +46,7 @@ https://leetcode-cn.com/problems/binary-tree-level-order-traversal/ 这样就实现了层序从左到右遍历二叉树。 -代码如下:**这份代码也可以作为二叉树层序遍历的模板,以后再打五个就靠它了**。 +代码如下:**这份代码也可以作为二叉树层序遍历的模板,以后再打七个就靠它了**。 ## C++代码 @@ -257,11 +268,108 @@ public: } return result; ``` + +# 116.填充每个节点的下一个右侧节点指针 + +给定一个完美二叉树,其所有叶子节点都在同一层,每个父节点都有两个子节点。二叉树定义如下: + +struct Node { + int val; + Node *left; + Node *right; + Node *next; +} +填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。 + +初始状态下,所有 next 指针都被设置为 NULL。 + + + +## 思路 + +本题依然是层序遍历,只不过在单层遍历的时候记录一下本层的头部节点,然后在遍历的时候让前一个节点指向本节点就可以了 + +## C++代码 + +``` +class Solution { +public: + Node* connect(Node* root) { + queue que; + if (root != NULL) que.push(root); + while (!que.empty()) { + int size = que.size(); + vector vec; + Node* nodePre; + Node* node; + for (int i = 0; i < size; i++) { + if (i == 0) { + nodePre = que.front(); // 取出一层的头结点 + que.pop(); + node = nodePre; + } else { + node = que.front(); + que.pop(); + nodePre->next = node; // 本层前一个节点next指向本节点 + nodePre = nodePre->next; + } + if (node->left) que.push(node->left); + if (node->right) que.push(node->right); + } + nodePre->next = NULL; // 本层最后一个节点指向NULL + } + return root; + + } +}; +``` + +# 117.填充每个节点的下一个右侧节点指针II + +## 思路 + +这道题目说是二叉树,但116题目说是完整二叉树,其实没有任何差别,一样的代码一样的逻辑一样的味道 + +## C++代码 + +``` +class Solution { +public: + Node* connect(Node* root) { + queue que; + if (root != NULL) que.push(root); + while (!que.empty()) { + int size = que.size(); + vector vec; + Node* nodePre; + Node* node; + for (int i = 0; i < size; i++) { + if (i == 0) { + nodePre = que.front(); // 取出一层的头结点 + que.pop(); + node = nodePre; + } else { + node = que.front(); + que.pop(); + nodePre->next = node; // 本层前一个节点next指向本节点 + nodePre = nodePre->next; + } + if (node->left) que.push(node->left); + if (node->right) que.push(node->right); + } + nodePre->next = NULL; // 本层最后一个节点指向NULL + } + return root; + } +}; +``` + + # 总结 二叉树的层序遍历,就是图论中的广度优先搜索在二叉树中的应用,需要借助队列来实现(此时是不是又发现队列的应用了)。 -学会二叉树的层序遍历,可以一口气撸完leetcode上五道题目: +虽然不能一口气打十个,打八个也还行。 * 102.二叉树的层序遍历 * 107.二叉树的层次遍历II @@ -269,8 +377,8 @@ public: * 637.二叉树的层平均值 * 429.N叉树的前序遍历 * 515.在每个树行中找最大值 - -虽然不能一口气打十个,打六个也还行。 +* 116. 填充每个节点的下一个右侧节点指针 +* 117.填充每个节点的下一个右侧节点指针II 如果非要打十个,还得找叶师傅! diff --git a/problems/0116.填充每个节点的下一个右侧节点指针.md b/problems/0116.填充每个节点的下一个右侧节点指针.md new file mode 100644 index 00000000..8dfa00e8 --- /dev/null +++ b/problems/0116.填充每个节点的下一个右侧节点指针.md @@ -0,0 +1,42 @@ + + +# 链接 +https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node/ + +## 思路 +同 117题目,代码都是一样的 + +## C++代码 + +``` + +class Solution { +public: + Node* connect(Node* root) { + queue que; + if (root != NULL) que.push(root); + while (!que.empty()) { + int size = que.size(); + vector vec; + Node* nodePre; + Node* node; + for (int i = 0; i < size; i++) { + if (i == 0) { + nodePre = que.front(); // 取出一层的头结点 + que.pop(); + node = nodePre; + } else { + node = que.front(); + que.pop(); + nodePre->next = node; // 本层前一个节点next指向本节点 + nodePre = nodePre->next; + } + if (node->left) que.push(node->left); + if (node->right) que.push(node->right); + } + nodePre->next = NULL; // 本层最后一个节点指向NULL + } + return root; + } +}; +``` diff --git a/problems/0117.填充每个节点的下一个右侧节点指针II.md b/problems/0117.填充每个节点的下一个右侧节点指针II.md new file mode 100644 index 00000000..41d52566 --- /dev/null +++ b/problems/0117.填充每个节点的下一个右侧节点指针II.md @@ -0,0 +1,39 @@ + +# 链接 +https://leetcode-cn.com/problems/populating-next-right-pointers-in-each-node-ii/ + +## 思路 + +## C++代码 + +``` +class Solution { +public: + Node* connect(Node* root) { + queue que; + if (root != NULL) que.push(root); + while (!que.empty()) { + int size = que.size(); + vector vec; + Node* nodePre; + Node* node; + for (int i = 0; i < size; i++) { + if (i == 0) { + nodePre = que.front(); // 取出一层的头结点 + que.pop(); + node = nodePre; + } else { + node = que.front(); + que.pop(); + nodePre->next = node; // 本层前一个节点next指向本节点 + nodePre = nodePre->next; + } + if (node->left) que.push(node->left); + if (node->right) que.push(node->right); + } + nodePre->next = NULL; // 本层最后一个节点指向NULL + } + return root; + } +}; +``` diff --git a/problems/0236.二叉树的最近公共祖先.md b/problems/0236.二叉树的最近公共祖先.md new file mode 100644 index 00000000..e222a34a --- /dev/null +++ b/problems/0236.二叉树的最近公共祖先.md @@ -0,0 +1,29 @@ +## 链接 +https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-tree/ + +## 思路 + +与其说是递归,不如说是回溯。(要好好讲讲回溯) + +如果左孩子出现在左子树,右孩子出现在右子树,那么该节点为最近公共祖先。 + +最后如果 + + + +## C++代码 + +``` +class Solution { +public: + TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) { + if (root == q || root == p || root == NULL) return root; + TreeNode* left = lowestCommonAncestor(root->left, p, q); + TreeNode* right = lowestCommonAncestor(root->right, p, q); + if (left != NULL && right != NULL) return root; + + if (left == NULL) return right; + return left; + } +}; +``` diff --git a/video/101.对称二叉树.gif b/video/101.对称二叉树.gif new file mode 100644 index 00000000..01ee5492 Binary files /dev/null and b/video/101.对称二叉树.gif differ