diff --git a/README.md b/README.md
index c241eaef..01e80da8 100644
--- a/README.md
+++ b/README.md
@@ -297,6 +297,8 @@
|[0349.两个数组的交集](https://github.com/youngyangyang04/leetcode/blob/master/problems/0349.两个数组的交集.md) |哈希表 |简单|**哈希**|
|[0350.两个数组的交集II](https://github.com/youngyangyang04/leetcode/blob/master/problems/0350.两个数组的交集II.md) |哈希表 |简单|**哈希**|
|[0383.赎金信](https://github.com/youngyangyang04/leetcode/blob/master/problems/0383.赎金信.md) |数组 |简单|**暴力** **字典计数** **哈希**|
+|[0404.左叶子之和](https://github.com/youngyangyang04/leetcode/blob/master/problems/0404.左叶子之和.md) |树/二叉树 |简单|**递归** **迭代**|
+|[0416.分割等和子集](https://github.com/youngyangyang04/leetcode/blob/master/problems/0416.分割等和子集.md) |动态规划 |中等|**背包问题/01背包**|
|[0429.N叉树的层序遍历](https://github.com/youngyangyang04/leetcode/blob/master/problems/0429.N叉树的层序遍历.md) |树 |简单|**队列/广度优先搜索**|
|[0434.字符串中的单词数](https://github.com/youngyangyang04/leetcode/blob/master/problems/0434.字符串中的单词数.md) |字符串 |简单|**模拟**|
|[0450.删除二叉搜索树中的节点](https://github.com/youngyangyang04/leetcode/blob/master/problems/0450.删除二叉搜索树中的节点.md) |树 |中等|**递归**|
diff --git a/pics/416.分割等和子集.png b/pics/416.分割等和子集.png
new file mode 100644
index 00000000..8d1b9ef6
Binary files /dev/null and b/pics/416.分割等和子集.png differ
diff --git a/pics/416.分割等和子集1.png b/pics/416.分割等和子集1.png
new file mode 100644
index 00000000..6be70dc6
Binary files /dev/null and b/pics/416.分割等和子集1.png differ
diff --git a/pics/530.二叉搜索树的最小绝对差.png b/pics/530.二叉搜索树的最小绝对差.png
new file mode 100644
index 00000000..04ca9a65
Binary files /dev/null and b/pics/530.二叉搜索树的最小绝对差.png differ
diff --git a/pics/617.合并二叉树.png b/pics/617.合并二叉树.png
new file mode 100644
index 00000000..182f959c
Binary files /dev/null and b/pics/617.合并二叉树.png differ
diff --git a/problems/0416.分割等和子集.md b/problems/0416.分割等和子集.md
new file mode 100644
index 00000000..1dc1b86a
--- /dev/null
+++ b/problems/0416.分割等和子集.md
@@ -0,0 +1,121 @@
+
+和Leetcode 473:火柴拼正方形和Leetcode 698:划分为k个相等的子集是
+
+
+## 思路
+
+这道题目是要找是否可以将这个数组分割成两个子集,使得两个子集的元素和相等。
+
+那么只要找到集合里能够出现 sum / 2 的集合,就算是可以分割成两个相同元素和子集了。
+
+本来是我是想用回溯暴力搜索出所有答案的,各种剪枝,还是超时了,不想在调了,放弃回溯,直接上01背包吧。
+
+如下的讲解中,我讲的重点是如何把01背包应用到此题,而不是讲01背包,如果对01背包本身还不理解的同学,需要额外学习一下基础知识,我后面也会在[代码随想录](https://img-blog.csdnimg.cn/20200815195519696.png)里深度讲解背包问题。
+
+### 背包问题
+
+背包问题,大家都知道,就是书包,书包可以容纳的体积n, 然后有各种商品,每一种商品体积为m,价值为z,问如果把书包塞满(不一定能塞满),书包里的商品最大价值总和是多少。
+
+**背包问题有多种背包方式,常见的有:01背包、完全背包、多重背包、分组背包和混合背包等等。**
+
+要注意背包问题问题中商品是不是可以重复放入。
+
+**即一个商品如果可以重复多次放入是完全背包,而只能放入一次是01背包,写法还是不一样的。**
+
+**要明确本题中我们要使用的是01背包,因为元素我们只能用一次。**
+
+为了让大家对背包问题有一个整体的了解,可以看如下图:
+
+
+
+回归主题:首先,本题要求集合里能否出现总和为 sum / 2 的子集。
+
+那么来一一对应一下本题,看看背包问题如果来解决。
+
+**只有确定了如下四点,才能把背包问题,套到本题上来。**
+
+* 背包的体积为sum / 2
+* 背包要放入的商品(集合里的元素)体积为 元素的数值,价值也为元素的数值
+* 背包如何正好装满,说明找到了总和为 sum / 2 的子集。
+* 背包中每一个元素一定是不可重复放入。
+
+
+定义里数组为dp[],dp[i] 表示 背包中放入体积为i的商品,最大价值为dp[i]。
+
+套到本题,dp[i]表示 背包中总和是i,最大可以凑成总和为i的元素总和为dp[i]。
+
+dp[i]一定是小于等于i的,因为背包不能装入超过自身体积的商品(这里理解为元素数值)。
+
+**如果dp[i] == i 说明,集合中的元素正好可以凑成总和i,理解这一点很重要。**
+
+## C++代码如下(详细注释 )
+```
+class Solution {
+public:
+ bool canPartition(vector& nums) {
+ int sum = 0;
+
+ // dp[i]中的i表示背包内总和
+ // 题目中说:每个数组中的元素不会超过 100,数组的大小不会超过 200
+ // 那么背包内总和不会大于20000,所以定义一个20000大的数组。
+ vector dp(20001, 0);
+ for (int i = 0; i < nums.size(); i++) {
+ sum += nums[i];
+ }
+ if (sum % 2 == 1) return false;
+ int target = sum / 2;
+
+ // 开始 01背包
+ for(int i = 0; i < nums.size(); i++) {
+ for(int j = target; j >= nums[i]; j--) { // 每一个元素一定是不可重复放入,所以从大到小遍历
+ dp[j] = max(dp[j], dp[j - nums[i]] + nums[i]);
+ }
+ }
+ // 集合中的元素正好可以凑成总和target
+ if (dp[target] == target) return true;
+ return false;
+ }
+};
+```
+
+### 暴力
+
+本来是想用回溯暴力搜索出所有答案的,各种剪枝,还是超时了,不想在调了,放弃回溯,直接上01背包吧。
+
+回溯搜索超时的代码如下:
+
+```
+class Solution {
+private:
+ int target;
+ bool backtracking(vector& nums, int startIndex, int pathSum, vector& used) {
+ for (int i = startIndex; i < nums.size(); i++) {
+ if (pathSum > target) break; // 剪枝
+ if (target < nums[i]) break; // 剪枝
+ if (i > 0 && nums[i] == nums[i - 1] && used[i - 1] == false) { // 去重
+ continue;
+ }
+ pathSum += nums[i];
+ used[i] = true;
+ if (pathSum == target) return true;
+ if (backtracking(nums, i + 1, pathSum, used)) return true;
+ used[i] = false;
+ pathSum -= nums[i];
+ }
+ return false;
+ }
+
+public:
+ bool canPartition(vector& nums) {
+ vector used(nums.size(), false);
+ sort(nums.begin(), nums.end());
+ int sum = 0;
+ for (int i = 0; i < nums.size(); i++) sum += nums[i];
+ if (sum % 2 == 1) return false;
+ target = sum / 2;
+ cout << "sum:" << sum << " target:" << target << endl;
+ return backtracking(nums, 0, 0, used);
+ }
+};
+
+```
diff --git a/problems/0530.二叉搜索树的最小绝对差.md b/problems/0530.二叉搜索树的最小绝对差.md
new file mode 100644
index 00000000..2cd19592
--- /dev/null
+++ b/problems/0530.二叉搜索树的最小绝对差.md
@@ -0,0 +1,111 @@
+
+## 题目地址
+
+https://leetcode-cn.com/problems/minimum-absolute-difference-in-bst/
+
+## 思路
+
+题目中要求在二叉搜索树上任意两节点的差的绝对值的最小值。
+
+**注意是二叉搜索树,**二叉搜索树可是有序的。
+
+遇到在二叉搜索树上求什么最值啊,差值之类的,就把它想成在一个有序数组上求最值,求差值,这样就简单多了。
+
+### 递归
+
+那么二叉搜索树如果采用中序遍历,其实就是一个有序数组。
+
+**在一个有序数组上求两个数最小差值,这是不是就是一道送分题了。**
+
+最直观的想法,就是把二叉搜索树转换成有序数组,然后遍历一遍数组,就统计出来最小差值了
+
+代码如下:
+
+```
+class Solution {
+private:
+vector vec;
+void traversal(TreeNode* root) {
+ if (root == NULL) return;
+ traversal(root->left);
+ vec.push_back(root->val); // 将二叉搜索树转换为有序数组
+ traversal(root->right);
+}
+public:
+ int getMinimumDifference(TreeNode* root) {
+ vec.clear();
+ traversal(root);
+ if (vec.size() < 2) return 0;
+ int result = INT_MAX;
+ for (int i = 1; i < vec.size(); i++) { // 统计有序数组的最小差值
+ result = min(result, vec[i] - vec[i-1]);
+ }
+ return result;
+ }
+};
+```
+
+以上代码是把二叉搜索树转化为有序数组了,其实在二叉搜素树中序遍历的过程中,我们就可以直接计算了。
+
+需要用一个pre节点记录一下,当前节点的前一个节点。
+
+如图:
+
+
+
+代码如下:
+
+```
+class Solution {
+private:
+int result = INT_MAX;
+TreeNode* pre;
+void traversal(TreeNode* cur) {
+ if (cur == NULL) return;
+ traversal(cur->left);
+ if (pre != NULL){
+ result = min(result, cur->val - pre->val);
+ }
+ pre = cur; // 记录前一个
+ traversal(cur->right);
+}
+public:
+ int getMinimumDifference(TreeNode* root) {
+ traversal(root);
+ return result;
+ }
+};
+```
+
+### 迭代
+
+看过这两篇[二叉树:听说递归能做的,栈也能做!](https://mp.weixin.qq.com/s/c_zCrGHIVlBjUH_hJtghCg),[二叉树:前中后序迭代方式的写法就不能统一一下么?](https://mp.weixin.qq.com/s/WKg0Ty1_3SZkztpHubZPRg)文章之后,不难写出两种中序遍历的迭代法。
+
+下面我给出其中的一种,代码如下:
+
+```
+class Solution {
+public:
+ int getMinimumDifference(TreeNode* root) {
+ stack st;
+ TreeNode* cur = root;
+ TreeNode* pre = NULL;
+ int result = INT_MAX;
+ while (cur != NULL || !st.empty()) {
+ if (cur != NULL) { // 指针来访问节点,访问到最底层
+ st.push(cur); // 将访问的节点放进栈
+ cur = cur->left; // 左
+ } else {
+ cur = st.top();
+ st.pop();
+ if (pre != NULL) {
+ result = min(result, cur->val - pre->val); // 中
+ }
+ pre = cur;
+ cur = cur->right; // 右
+ }
+ }
+ return result;
+ }
+};
+```
diff --git a/problems/0617.合并二叉树.md b/problems/0617.合并二叉树.md
index 92eb7d11..cfecb9a1 100644
--- a/problems/0617.合并二叉树.md
+++ b/problems/0617.合并二叉树.md
@@ -1,13 +1,29 @@
## 题目地址
https://leetcode-cn.com/problems/merge-two-binary-trees/
-## 思路
+> 合并一下
+
+# 617.合并二叉树
+
+给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
+
+你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠,那么将他们的值相加作为节点合并后的新值,否则不为 NULL 的节点将直接作为新二叉树的节点。
+
+示例 1:
+
+
+
+注意: 合并必须从两个树的根节点开始。
+
+# 思路
相信这道题目很多同学疑惑的点是如何同时遍历两个二叉树呢?
其实和遍历一个树逻辑是一样的,只不过传入两个树的节点,同时操作。
-那么前中后序应该使用哪种遍历呢?
+## 递归
+
+二叉树使用递归,就要想使用前中后哪种遍历方式?
**本题使用哪种遍历都是可以的!**
@@ -15,12 +31,12 @@ https://leetcode-cn.com/problems/merge-two-binary-trees/
动画如下:
-
那么我们来按照递归三部曲来解决:
1. **确定递归函数的参数和返回值:**
+
首先那么要合入两个二叉树,那么参数至少是要传入两个二叉树的根节点,返回值就是合并之后二叉树的根节点。
代码如下:
@@ -31,9 +47,9 @@ TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
2. **确定终止条件:**
-因为是传入了两个树,那么就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就应该是 t2 了啊(如果t2也为NULL也无所谓)。
+因为是传入了两个树,那么就有两个树遍历的节点t1 和 t2,如果t1 == NULL 了,两个树合并就应该是 t2 了啊(如果t2也为NULL也无所谓,合并之后就是NULL)。
-反过来如果t2 == NULL,那么两个数合并就是t1(如果t1也为NULL也无所谓)。
+反过来如果t2 == NULL,那么两个数合并就是t1(如果t1也为NULL也无所谓,合并之后就是NULL)。
代码如下:
@@ -45,14 +61,18 @@ if (t2 == NULL) return t1; // 如果t2为空,合并之后就应该是t1
3. **确定单层递归的逻辑:**
-单层递归的逻辑就比较好些了,这里我们用重复利用一下t1这个树,t1就是合并之后树的根节点(所谓的修改了元数据的结构)。
+单层递归的逻辑就比较好些了,这里我们用重复利用一下t1这个树,t1就是合并之后树的根节点(就是修改了原来树的结构)。
那么单层递归中,就要把两棵树的元素加到一起。
```
t1->val += t2->val;
```
-那么此时t1 的左子树 应该是 合并 t1左子树 t2左子树之后的左子树,t1 的右子树 应该是 合并 t1右子树 t2右子树之后的右子树。
+接下来t1 的左子树是:合并 t1左子树 t2左子树之后的左子树。
+
+t1 的右子树:是 合并 t1右子树 t2右子树之后的右子树。
+
+最终t1就是合并之后的根节点。
代码如下:
@@ -62,7 +82,7 @@ t1->val += t2->val;
return t1;
```
-此时前序遍历,修改原输入树结构的完整代码就写出来了,如下:
+此时前序遍历,完整代码就写出来了,如下:
```
class Solution {
@@ -79,7 +99,7 @@ public:
};
```
-那么中序遍历可不可以呢,也是可以的,代码如下:
+那么中序遍历也是可以的,代码如下:
```
class Solution {
@@ -96,7 +116,7 @@ public:
};
```
-后序遍历呢,依然可以,代码如下:
+后序遍历依然可以,代码如下:
```
class Solution {
@@ -115,38 +135,10 @@ public:
**但是前序遍历是最好理解的,我建议大家用前序遍历来做就OK。**
-**那么如下还总结了四种方法,递归的方式均使用了前序遍历,此时大家应该知道了,以下每一种递归的方法都可以换成中序和后序遍历,所以本题的解法是很多的。**
+如上的方法修改了t1的结构,当然也可以不修改t1和t2的结构,重新定一个树。
-**其实这道题目迭代法实现是比较困难的,大家可以试一试,是一道不错的面试进阶题目。**
+不修改输入树的结构,前序遍历,代码如下:
-四种写法如下:
-
-1. 递归修改了输入树的结构
-2. 递归不修改树的结构
-3. 递归,一波指针的操作,自己写的野路子(可以用来深度理解一下C++的指针)
-4. 迭代(这应该是最简单直观的迭代法代码了,一看就懂)
-
-## C++代码
-
-### 递归
-
-修改了输入树的结构,前序遍历
-```
-class Solution {
-public:
- TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
- if (t1 == NULL) return t2; // 如果t1为空,合并之后就应该是t2
- if (t2 == NULL) return t1; // 如果t2为空,合并之后就应该是t1
- // 修改了t1的数值和结构
- t1->val += t2->val;
- t1->left = mergeTrees(t1->left, t2->left);
- t1->right = mergeTrees(t1->right, t2->right);
- return t1;
- }
-};
-```
-
-不修改输入树的结构,前序遍历
```
class Solution {
public:
@@ -163,9 +155,61 @@ public:
};
```
-一波指针的操作,自己写的野路子
-想要更改二叉树的值,应该传入指向指针的指针, 如果process(t1, t2);这么写的话,其实只是传入的一个int型的指针,并没有传入地址,要传入指向指针的指针才能完成对t1的修改。
-(前序遍历)
+## 迭代法
+
+使用迭代法,如何同时处理两棵树呢?
+
+思路我们在[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)中的迭代法已经讲过一次了,求二叉树对称的时候就是把两个树的节点同时加入队列进行比较。
+
+本题我们也使用队列,模拟的层序遍历,代码如下:
+
+```
+class Solution {
+public:
+ TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
+ if (t1 == NULL) return t2;
+ if (t2 == NULL) return t1;
+ queue que;
+ que.push(t1);
+ que.push(t2);
+ while(!que.empty()) {
+ TreeNode* node1 = que.front(); que.pop();
+ TreeNode* node2 = que.front(); que.pop();
+ // 此时两个节点一定不为空,val相加
+ node1->val += node2->val;
+
+ // 如果两棵树左节点都不为空,加入队列
+ if (node1->left != NULL && node2->left != NULL) {
+ que.push(node1->left);
+ que.push(node2->left);
+ }
+ // 如果两棵树右节点都不为空,加入队列
+ if (node1->right != NULL && node2->right != NULL) {
+ que.push(node1->right);
+ que.push(node2->right);
+ }
+
+ // 当t1的左节点 为空 t2左节点不为空,就赋值过去
+ if (node1->left == NULL && node2->left != NULL) {
+ node1->left = node2->left;
+ }
+ // 当t1的右节点 为空 t2右节点不为空,就赋值过去
+ if (node1->right == NULL && node2->right != NULL) {
+ node1->right = node2->right;
+ }
+ }
+ return t1;
+ }
+};
+```
+
+# 拓展
+
+当然也可以秀一波指针的操作,这是我写的野路子,大家就随便看看就行了,以防带跑遍了。
+
+如下代码中,想要更改二叉树的值,应该传入指向指针的指针。
+
+代码如下:(前序遍历)
```
class Solution {
public:
@@ -190,45 +234,17 @@ public:
}
};
```
-### 迭代
-这应该是最简单直观的迭代法了,模拟的层序遍历。
-```
-class Solution {
-public:
- TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
- if (t1 == NULL) return t2;
- if (t2 == NULL) return t1;
- queue que;
- que.push(t1);
- que.push(t2);
- while(!que.empty()) {
- TreeNode* node1 = que.front(); que.pop();
- TreeNode* node2 = que.front(); que.pop();
- // 此时两个节点一定不为空,val相加
- node1->val += node2->val;
- // 如果左节点都不为空,加入队列
- if (node1->left != NULL && node2->left != NULL) {
- que.push(node1->left);
- que.push(node2->left);
- }
- // 如果右节点都不为空,加入队列
- if (node1->right != NULL && node2->right != NULL) {
- que.push(node1->right);
- que.push(node2->right);
- }
- // 当t1的左节点 为空 t2左节点不为空,就赋值过去
- if (node1->left == NULL && node2->left != NULL) {
- node1->left = node2->left;
- }
- // 当t1的右节点 为空 t2右节点不为空,就赋值过去
- if (node1->right == NULL && node2->right != NULL) {
- node1->right = node2->right;
- }
- }
- return t1;
- }
-};
-```
+# 总结
+
+合并二叉树,也是二叉树操作的经典题目,如果没有接触过的话,其实并不简单,因为我们习惯了操作一个二叉树,一起操作两个二叉树,还会有点懵懵的。
+
+这不是我们第一次操作两颗二叉树了,在[二叉树:我对称么?](https://mp.weixin.qq.com/s/Kgf0gjvlDlNDfKIH2b1Oxg)中也一起操作了两棵二叉树。
+
+迭代法中,一般一起操作两个树都是使用队列模拟类似层序遍历,同时处理两个树的节点,这种方式最好理解,如果用模拟递归的思路的话,要复杂一些。
+
+最后拓展中,我给了一个操作指针的野路子,大家随便看看就行了,如果学习C++的话,可以在去研究研究。
+
+就酱,学到了的话,就转发给身边需要的同学吧!
> 更多算法干货文章持续更新,可以微信搜索「代码随想录」第一时间围观,关注后,回复「Java」「C++」 「python」「简历模板」「数据结构与算法」等等,就可以获得我多年整理的学习资料。