This commit is contained in:
youngyangyang04
2020-10-12 10:20:23 +08:00
parent aca2679fb6
commit 1e0c5e671b
8 changed files with 332 additions and 82 deletions

View File

@ -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) |树 |中等|**递归**|

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 122 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

View File

@ -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背包因为元素我们只能用一次。**
为了让大家对背包问题有一个整体的了解,可以看如下图:
<img src='../pics/416.分割等和子集1.png' width=600> </img></div>
回归主题:首先,本题要求集合里能否出现总和为 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<int>& nums) {
int sum = 0;
// dp[i]中的i表示背包内总和
// 题目中说:每个数组中的元素不会超过 100数组的大小不会超过 200
// 那么背包内总和不会大于20000所以定义一个20000大的数组。
vector<int> 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<int>& nums, int startIndex, int pathSum, vector<bool>& 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<int>& nums) {
vector<bool> 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);
}
};
```

View File

@ -0,0 +1,111 @@
## 题目地址
https://leetcode-cn.com/problems/minimum-absolute-difference-in-bst/
## 思路
题目中要求在二叉搜索树上任意两节点的差的绝对值的最小值。
**注意是二叉搜索树,**二叉搜索树可是有序的。
遇到在二叉搜索树上求什么最值啊,差值之类的,就把它想成在一个有序数组上求最值,求差值,这样就简单多了。
### 递归
那么二叉搜索树如果采用中序遍历,其实就是一个有序数组。
**在一个有序数组上求两个数最小差值,这是不是就是一道送分题了。**
最直观的想法,就是把二叉搜索树转换成有序数组,然后遍历一遍数组,就统计出来最小差值了
代码如下:
```
class Solution {
private:
vector<int> 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节点记录一下当前节点的前一个节点。
如图:
<img src='../pics/530.二叉搜索树的最小绝对差.png' width=600> </img></div>
代码如下:
```
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<TreeNode*> 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;
}
};
```

View File

@ -1,13 +1,29 @@
## 题目地址
https://leetcode-cn.com/problems/merge-two-binary-trees/
## 思路
> 合并一下
# 617.合并二叉树
给定两个二叉树,想象当你将它们中的一个覆盖到另一个上时,两个二叉树的一些节点便会重叠。
你需要将他们合并为一个新的二叉树。合并的规则是如果两个节点重叠那么将他们的值相加作为节点合并后的新值否则不为 NULL 的节点将直接作为新二叉树的节点。
示例 1:
<img src='../pics/617.合并二叉树.png' width=600> </img></div>
注意: 合并必须从两个树的根节点开始。
# 思路
相信这道题目很多同学疑惑的点是如何同时遍历两个二叉树呢?
其实和遍历一个树逻辑是一样的,只不过传入两个树的节点,同时操作。
那么前中后序应该使用哪种遍历呢?
## 递归
二叉树使用递归,就要想使用前中后哪种遍历方式?
**本题使用哪种遍历都是可以的!**
@ -15,12 +31,12 @@ https://leetcode-cn.com/problems/merge-two-binary-trees/
动画如下:
<img src='../video/617.合并二叉树.gif' width=600> </img></div>
那么我们来按照递归三部曲来解决:
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<TreeNode*> 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<TreeNode*> 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」「简历模板」「数据结构与算法」等等就可以获得我多年整理的学习资料。