BM37.二叉搜索数的最近公共祖先
方法一:一次遍历 二叉树递归
思路:
我们也可以利用二叉搜索树的性质:对于某一个节点若是p与q都小于等于这个这个节点值,说明p、q都在这个节点的左子树,而最近的公共祖先也一定在这个节点的左子树;若是p与q都大于等于这个节点,说明p、q都在这个节点的右子树,而最近的公共祖先也一定在这个节点的右子树。而若是对于某个节点,p与q的值一个大于等于节点值,一个小于等于节点值,说明它们分布在该节点的两边,而这个节点就是最近的公共祖先,因此从上到下的其他祖先都将这个两个节点放到同一子树,只有最近公共祖先会将它们放入不同的子树,每次进入一个子树又回到刚刚的问题,因此可以使用递归。
具体做法:
- step 1:首先检查空节点,空树没有公共祖先。
- step 2:对于某个节点,比较与p、q的大小,若p、q在该节点两边说明这就是最近公共祖先。
- step 3:如果p、q都在该节点的左边,则递归进入左子树。
- step 4:如果p、q都在该节点的右边,则递归进入右子树。
class Solution {
public:
int lowestCommonAncestor(TreeNode* root, int p, int q) {
//空树找不到公共祖先
if(root == NULL)
return -1;
//pq在该节点两边说明这就是最近公共祖先
if((p >= root->val && q <= root->val) || (p <= root->val && q >= root->val))
return root->val;
//pq都在该节点的左边
else if(p <= root->val && q <= root->val)
//进入左子树
return lowestCommonAncestor(root->left, p, q);
//pq都在该节点的右边
else
//进入右子树
return lowestCommonAncestor(root->right, p, q);
}
};
方法二:搜索路径比较
思路:
二叉搜索树没有相同值的节点,因此分别从根节点往下利用二叉搜索树较大的数在右子树,较小的数在左子树,可以轻松找到p、q:
1 2 3 4 5 6 7 8 9 10 |
|
直接得到从根节点到两个目标节点的路径,这样我们利用路径比较就可以找到最近公共祖先。
具体做法:
- step 1:根据二叉搜索树的性质,从根节点开始查找目标节点,当前节点比目标小则进入右子树,当前节点比目标大则进入左子树,直到找到目标节点。这个过程成用数组记录遇到的元素。
- step 2:分别在搜索二叉树中找到p和q两个点,并记录各自的路径为数组。
- step 3:同时遍历两个数组,比较元素值,最后一个相等的元素就是最近的公共祖先。
class Solution {
public:
//求得根节点到目标节点的路径
vector<int> getPath(TreeNode* root, int target) {
vector<int> path;
TreeNode* node = root;
//节点值都不同,可以直接用值比较
while(node->val != target){
path.push_back(node->val);
//小的在左子树
if(target < node->val)
node = node->left;
//大的在右子树
else
node = node->right;
}
path.push_back(node->val);
return path;
}
int lowestCommonAncestor(TreeNode* root, int p, int q) {
//求根节点到两个节点的路径
vector<int> path_p = getPath(root, p);
vector<int> path_q = getPath(root, q);
int res;
//比较两个路径,找到第一个不同的点
for(int i = 0; i < path_p.size() && i < path_q.size(); i++){
if(path_p[i] == path_q[i])
//最后一个相同的节点就是最近公共祖先
res = path_p[i];
else
break;
}
return res;
}
};
BM38.在二叉树中找到两个结点的最近公共祖先
方法二:二叉树递归
思路:
我们也可以讨论几种情况:
- step 1:如果o1和o2中的任一个和root匹配,那么root就是最近公共祖先。
- step 2:如果都不匹配,则分别递归左、右子树。
- step 3:如果有一个节点出现在左子树,并且另一个节点出现在右子树,则root就是最近公共祖先.
- step 4:如果两个节点都出现在左子树,则说明最低公共祖先在左子树中,否则在右子树。
- step 5:继续递归左、右子树,直到遇到step1或者step3的情况。
class Solution {
public:
int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
//该子树没找到,返回-1
if(root == NULL)
return -1;
//该节点是其中某一个节点
if(root->val == o1 || root->val == o2)
return root->val;
//左子树寻找公共祖先
int left = lowestCommonAncestor(root->left, o1, o2);
//右子树寻找公共祖先
int right = lowestCommonAncestor(root->right, o1, o2);
//左子树为没找到,则在右子树中
if(left == -1)
return right;
//右子树没找到,则在左子树中
if(right == -1)
return left;
//否则是当前节点
return root->val;
}
};
方法一:深度优先搜索(dfs)
深度优先搜索一般用于树或者图的遍历,其他有分支的(如二维矩阵)也适用。它的原理是从初始点开始,一直沿着同一个分支遍历,直到该分支结束,然后回溯到上一级继续沿着一个分支走到底,如此往复,直到所有的节点都有被访问到。
思路:
既然要找到二叉树中两个节点的最近公共祖先,那我们可以考虑先找到两个节点全部祖先,可以得到从根节点到目标节点的路径,然后依次比较路径得出谁是最近的祖先。
找到两个节点的所在可以深度优先搜索遍历二叉树所有节点进行查找。
具体做法:
- step 1:利用dfs求得根节点到两个目标节点的路径:每次选择二叉树的一棵子树往下找,同时路径数组增加这个遍历的节点值。
- step 2:一旦遍历到了叶子节点也没有,则回溯到父节点,寻找其他路径,回溯时要去掉数组中刚刚加入的元素。
- step 3:然后遍历两条路径数组,依次比较元素值。
- step 4:找到两条路径第一个不相同的节点即是最近公共祖先。
class Solution {
public:
//记录是否找到到o的路径
bool flag = false;
//求得根节点到目标节点的路径
void dfs(TreeNode* root, vector<int>& path, int o){
if(flag || root == NULL)
return;
path.push_back(root->val);
//节点值都不同,可以直接用值比较
if(root->val == o){
flag = true;
return;
}
//dfs遍历查找
dfs(root->left, path, o);
dfs(root->right, path, o);
//找到
if(flag)
return;
//该子树没有,回溯
path.pop_back();
}
int lowestCommonAncestor(TreeNode* root, int o1, int o2) {
vector<int> path1, path2;
//求根节点到两个节点的路径
dfs(root, path1, o1);
//重置flag,查找下一个
flag = false;
dfs(root, path2, o2);
int res;
//比较两个路径,找到第一个不同的点
for(int i = 0; i < path1.size() && i < path2.size(); i++){
if(path1[i] == path2[i])
//最后一个相同的节点就是最近公共祖先
res = path1[i];
else
break;
}
return res;
}
};
BM39.序列化二叉树
思路:
序列化即将二叉树的节点值取出,放入一个字符串中,我们可以按照前序遍历的思路,遍历二叉树每个节点,并将节点值存储在字符串中,我们用‘#’表示空节点,用‘!'表示节点与节点之间的分割。
反序列化即根据给定的字符串,将二叉树重建,因为字符串中的顺序是前序遍历,因此我们重建的时候也是前序遍历,即可还原。
具体做法:
- step 1:优先处理序列化,首先空树直接返回“#”,然后调用SerializeFunction函数前序递归遍历二叉树。
1 |
|
- step 2:SerializeFunction函数负责前序递归,根据“根左右”的访问次序,优先访问根节点,遇到空节点在字符串中添加‘#’,遇到非空节点,添加相应节点数字和‘!’,然后依次递归进入左子树,右子树。
1 2 3 4 5 6 |
|
- step 3:创建全局变量index表示序列中的下标(C++中直接指针完成)。
- step 4:再处理反序列化,读入字符串,如果字符串直接为"#",就是空树,否则还是调用DeserializeFunction函数前序递归建树。
1 |
|
- step 5:DeserializeFunction函数负责前序递归构建树,遇到‘#’则是空节点,遇到数字则根据感叹号分割,将字符串转换为数字后加入新创建的节点中,依据“根左右”,创建完根节点,然后依次递归进入左子树、右子树创建新节点。
1 2 3 4 5 |
|
class Solution {
public:
//处理序列化的功能函数(递归)
void SerializeFunction(TreeNode* root, string& str){
//如果指针为空,表示左子节点或右子节点为空,用#表示
if(root == NULL){
str += '#';
return;
}
//根节点
string temp = to_string(root->val);
str += temp + '!';// 加!,区分节点
//左子树
SerializeFunction(root->left, str);
//右子树
SerializeFunction(root->right, str);
}
char* Serialize(TreeNode *root) {
//处理空树
if(root == NULL)
return "#";
string res;
SerializeFunction(root, res);
//把str转换成char
char* charRes = new char[res.length() + 1];
strcpy(charRes, res.c_str());
charRes[res.length()] = '\0';
return charRes;
}
//处理反序列化的功能函数(递归)
TreeNode* DeserializeFunction(char** str){
//到达叶节点时,构建完毕,返回继续构建父节点
//双**表示取值
if(**str == '#'){
(*str)++;
return NULL;
}
//数字转换
int val = 0;
while(**str != '!' && **str != '\0'){
val = val * 10 + ((**str) - '0');
(*str)++;
}
TreeNode* root = new TreeNode(val);
//序列到底了,构建完成
if(**str == '\0')
return root;
else
(*str)++;
//反序列化与序列化一致,都是前序
root->left = DeserializeFunction(str);
root->right = DeserializeFunction(str);
return root;
}
TreeNode* Deserialize(char *str) {
//空序列对应空树
if(str == "#"){
return NULL;
}
TreeNode* res = DeserializeFunction(&str);
return res;
}
};