算法学习记录| 2023.X.XX| 章节DayX| 题目号.题目标题 & 题目号.题目标题
617.合并二叉树
题目链接
思路1:递归
使用DFS
可以将第一棵树当做主树,在递归时全都返回这棵树,将第二棵树的信息加到第一棵上。
比较关键的是终止条件,那就是root1为空时返回root2,反之亦然,即使同时为NULL也不影响结果
代码
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
if (root1 == NULL) // 如果root1为空,合并之后就应该是root2(root2为NULL也不影响,都是NULL)
return root2;
if (root2 == NULL)
return root1;
root1 -> val += root2 -> val; //使用root1的树当主树
root1 -> left = mergeTrees(root1 -> left, root2 -> left); //使用root1的树当主树
root1 -> right = mergeTrees(root1 -> right, root2 -> right);
return root1;
}
};
思路2:迭代法(层序遍历)
和101. 对称二叉树的迭代法很像,把需要同时处理的两个点同时放入队列就可以一起处理。
同样是拿root1当主树,因此除了两树的左/右节点都不为空时需要入队列外,当root1的左/右节点为空而root2的左/右节点不为空时,需要把root2赋给root1。
代码
class Solution {
public:
TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {
//根为空的话则直接返回另一棵树即可
if ( root1 == NULL )
return root2;
if ( root2 == NULL )
return root1;
queue<TreeNode*> que;
que.push(root1);
que.push(root2);
while ( !que.empty() ){
TreeNode* node1 = que.front();
que.pop();
TreeNode* node2 = que.front();
que.pop();
node1 -> val += node2 -> val; //合并,此时肯定都不为NULL
if( node1 -> left && node2 -> left ){ //如果两棵树左节点都不为空,加入队列
que.push(node1 -> left);
que.push(node2 -> left);
}
if ( node1 -> right && node2 -> right ){ //如果两棵树右节点都不为空,加入队列
que.push(node1 -> right);
que.push(node2 -> right);
}
//如果只有node2的左/右子树为空则不需要特殊处理,因为都是返回的root1的树,只有node1的左/右为空而node2不空才需要下列处理
if(!node1 -> left && node2 -> left){ //当root1的左节点为空,root2左节点不为空,就赋值过去
node1 -> left = node2 -> left;
}
if(!node1 -> right && node2 -> right){ //当root1的右节点为空,root2右节点不为空,就赋值过去
node1 -> right = node2 -> right;
}
}
return root1;
}
};
总结
700.二叉搜索树中的搜索
题目链接
思路1:递归
利用二叉搜索树的特性可以在比较每一个节点时只将递归引入其中一边的子树
代码
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
if (root == NULL) //根节点为空直接返回空(用root也一样)
return NULL;
if (root -> val == val) //根结点正好为目标值则返回该树
return root;
if (root -> val > val)
return searchBST(root -> left, val);
if (root -> val < val)
return searchBST(root -> right, val);
return NULL;
}
};
思路2:迭代法(利用二叉搜索树特性)
一提到二叉树遍历的迭代法,可能立刻想起使用栈来模拟深度遍历,使用队列来模拟广度遍历。
但对于二叉搜索树,因为它的特殊性,也就是节点的有序性,可以不使用辅助栈或者队列就可以写出迭代法。
对于一般二叉树,递归过程中还有回溯的过程,例如走一个左方向的分支走到头了,那么要调头,再走右分支。但对于二叉搜索树,不需要回溯的过程,因为节点的有序性就帮我们确定了搜索的方向。
代码
class Solution {
public:
TreeNode* searchBST(TreeNode* root, int val) {
while(root != NULL){
if(root -> val > val)
root = root -> left;
else if(root -> val < val)
root = root -> right;
else
return root;
}
return NULL;
}
};
总结
98.验证二叉搜索树
题目链接
陷阱
- 不能单纯的比较左节点小于中间节点,右节点大于中间节点就完事了,因为需要左子树所有节点小于中间节点,右子树所有节点大于中间节点,因此类似下面的代码就是容易犯的错误
if (root->val > root->left->val && root->val < root->right->val)
return true;
else
return false;
- 样例中最小节点可能是int的最小值,如果这样使用最小的int来比较也是不行的。此时可以初始化比较元素为longlong的最小值。
但如果样例中根节点的val 可能是longlong的最小值,就通过记录前一个节点在比较时用
思路1:转化为判断数组是否递增
中序遍历下,输出的二叉搜索树节点的数值是有序序列,因此验证二叉搜索树就变成了判断一个序列是否为递增的。
代码
class Solution {
public:
vector<int> array; //保存中序遍历后的数组
void traversal(TreeNode* root){
if(root == NULL)
return;
traversal(root -> left);
array.push_back(root -> val);
traversal(root -> right);
}
bool isValidBST(TreeNode* root) {
traversal(root);
for(int i = 0; i < array.size() - 1; i++){
if(array[i] >= array[i + 1])
return false;
}
return true;
}
};
思路2:递归法
这次的递归主要针对陷阱2,第一份代码为将最大值先初始化为longlong最小值来进行比较的开始,第二份代码则优化为取前一个节点的数值来比较
//代码1:将最大值先初始化为longlong最小值
class Solution {
public:
long long maxVal = LONG_MIN; // 因为后台测试数据中有int最小值
bool isValidBST(TreeNode* root) {
if (root == NULL) return true;
bool left = isValidBST(root->left);
// 中序遍历,验证遍历的元素是不是从小到大
if (maxVal < root->val) maxVal = root->val;
else return false;
bool right = isValidBST(root->right);
return left && right;
}
};
//代码2:取前一个节点的数值来比较
class Solution {
public:
TreeNode* pre = NULL; // 用来记录前一个节点
bool isValidBST(TreeNode* root) {
if (root == NULL) return true;
bool left = isValidBST(root->left);
if (pre != NULL && pre->val >= root->val) return false;
pre = root; // 记录前一个节点
bool right = isValidBST(root->right);
return left && right;
}
};
思路3:迭代法
通过迭代法来模拟二叉树的中序遍历,来完成类似思路2一样的实现
代码
class Solution {
public:
bool isValidBST(TreeNode* root) {
stack<TreeNode*> st;
TreeNode* cur = root;
TreeNode* pre = NULL; //记录前一个节点
while(cur != NULL || !st.empty()){
if(cur != NULL){
st.push(cur);
cur = cur -> left; //左
}
else{
cur = st.top(); //中
st.pop();
if ( pre != NULL && cur -> val <= pre -> val)
return false;
pre = cur; //保存前一个访问的节点
cur = cur -> right; //右
}
}
return true;
}
};
总结
- 二叉搜索树的中序遍历是有序数组需要意识到并且善加利用
- 陷阱1非常容易被忽略,也就是只处理每个节点和它左右节点的大小关系,而忘了整个子树的所有节点和根结点的大小关系也需要确定
530.二叉搜索树的最小绝对差
题目链接
思路1:递归
看到二叉搜索树第一反应就是中序遍历转换成有序数组,然后再对数组进行数据处理,很简单因此代码不写了。
但其实在中序遍历过程中就可以直接计算了,和98.验证二叉搜索树一样也是需要一个pre节点来记录cur节点的前一个节点。
由于是二叉搜索树,因此按中序遍历,cur的值一定大于pre的值,因此不需要abs()函数获取绝对值
代码
class Solution {
public:
int result = INT_MAX; //结果要求最小值,那就先初始化为最大值
TreeNode* pre = NULL; //用于记录当前节点的前一个节点
void dfs(TreeNode* cur){
if (cur == NULL)
return;
dfs(cur -> left); //左
if (pre != NULL) //中
result = min(result, abs(cur -> val - pre -> val));
pre = cur; //记录前一个节点
dfs(cur -> right); //右
}
int getMinimumDifference(TreeNode* root) {
dfs(root);
return result;
}
};
思路2:迭代
同样是利用迭代实现中序遍历,还是需要一个pre节点,在遍历过程中取得计算结果。
由于是二叉搜索树,因此按中序遍历,cur的值一定大于pre的值,因此不需要abs()函数获取绝对值
代码
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;
}
};