给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
百度百科中最近公共祖先的定义为:“对于有根树 T 的两个节点 p、q,最近公共祖先表示为一个节点 x,满足 x 是 p、q 的祖先且 x 的深度尽可能大(一个节点也可以是它自己的祖先)。”
方法1——存储父节点:
我的思路是从根节点开始遍历树,找到两个节点pq,并将他们的所有祖先(包括自己)自顶向下记录下来,然后比较祖先序列,从后到前,找到相同的则是最近公共祖先,但是很遗憾,这样TLE了,dfs用时并不多,list的size应该在O(logn)级别,但是list两轮循环下,就有可能超过10^7数量级,就会超时了。
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
List<TreeNode>list1=new ArrayList<>();
List<TreeNode>list2=new ArrayList<>();
dfs(root, p.val, list1);
dfs(root, q.val, list2);
for(int i=list1.size()-1;i>=0;i--){
for(int j=list2.size()-1;j>=0;j--){
if(list1.get(i).val==list2.get(j).val){
return list1.get(i);
}
}
}
return null;
}
void dfs(TreeNode root,int val,List<TreeNode> list){
if(root==null)return;
list.add(root);
if(root.val==val)return;
dfs(root.left, val, list);
dfs(root.left, val, list);
dfs(root.right, val, list);
dfs(root.right, val, list);
list.remove(list.size()-1);
return;
}
}
但这个方法是可行的,想要降低复杂度,我们就要使用哈希表来存储结点和父母之间的关系,然后自底向上遍历,寻找公共点,降低存取复杂度,这样就可以AC了
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//map中记录了任意一个数字和它的父节点的对应关系
Map<TreeNode, TreeNode> map = new HashMap<>();
//set用来记录p的遍历结果,如果和q有重合证明找到了目标点
Set<Integer> visited = new HashSet<Integer>();
dfs(root,map);
//自下而上将p的所有祖先加入set中,注意根节点除外
while(map.containsKey(p)){
visited.add(p.val);
p=map.get(p);
}
//自下而上将p的所有祖先加入set中,注意根节点除外
//如果set中已经有该结点,那么即为目标节点
while(map.containsKey(q)){
if(visited.contains(q.val)){
return q;
}
q=map.get(q);
}
//注意map中不含有根节点,所以如果之前遍历没找到,那么根节点一定是根节点
return root;
}
//dfs,初始化map的数据
void dfs(TreeNode root,Map<TreeNode, TreeNode> map){
if(root.left!=null){
map.put(root.left, root);
dfs(root.left, map);
}
if(root.right!=null){
map.put(root.right, root);
dfs(root.right, map);
}
}
}
方法2——递归查找
想要递归,那我们要确定递归思路,二叉树递归肯定要通过左右节点递归,如何确定返回值呢?
边界条件:1.空返回null 2.p/q返回p/q
一般情况:1.左右都为空返回null 2.都不空返回root 3.一空一不空返回不空的那个
代码如下:
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
//边界条件
if(root==null||root.equals(p)||root.equals(q)){
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;
}
if(right==null){
return left;
}
return null;
}
}
序列化是将一个数据结构或者对象转换为连续的比特位的操作,进而可以将转换后的数据存储在一个文件或者内存中,同时也可以通过网络传输到另一个计算机环境,采取相反方式重构得到原数据。
请设计一个算法来实现二叉树的序列化与反序列化。这里不限定你的序列 / 反序列化算法执行逻辑,你只需要保证一个二叉树可以被序列化为一个字符串并且将这个字符串反序列化为原始的树结构。
提示: 输入输出格式与 LeetCode 目前使用的方式一致,详情请参阅 LeetCode 序列化二叉树的格式。你并非必须采取这种方式,你也可以采用其他的方法解决这个问题。
比较容易想到的方法就是,我们用诸如BFS\DFS这样的方法来遍历二叉树,再将遍历结果转化为字符串即可,期间对字符串稍加修改方便还原为二叉树
方法1:BFS
序列化简单,而反序列化的关键在于,我们保证序列化时每个节点都有孩子,如果是叶子节点,则加上两个"X"存储起来,反序列化时转化为null即可,其中用到了正则表达式的split函数,然后我们要认识到反序列化的过程实质上是有一棵树被储存在了arr中,我们要BFS这棵树!这样就容易理解了
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
if (root == null) {
return "";
}
return bfs(root);
}
public String bfs(TreeNode root) {
StringBuilder sb = new StringBuilder();
Deque<TreeNode> queue = new LinkedList<>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node == null) {
sb.append("X,");
} else {
sb.append(node.val + ",");
queue.offer(node.left);
queue.offer(node.right);
}
}
return sb.toString();
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
if(data==""){
return null;
}
//分割字符串,分割为多个节点,X对应null,数字对应普通节点
String[]arr=data.split(",");
int index=0;
//事实上我们已经将整个树结构存在了arr中,然后用bfs的方法来遍历这棵"树"
//BFS自然要有队列
Queue<TreeNode>queue=new LinkedList<>();
TreeNode root=new TreeNode(Integer.parseInt(arr[index++]));
queue.offer(root);
while(!queue.isEmpty()){
TreeNode node=queue.poll();
if(node==null)continue;
String left=arr[index++];
String right=arr[index++];
if("X".equals(left)){
node.left=null;
}else{
node.left=new TreeNode(Integer.parseInt(left));
}
if("X".equals(right)){
node.right=null;
}else{
node.right=new TreeNode(Integer.parseInt(right));
}
queue.offer(node.left);
queue.offer(node.right);
}
return root;
}
}
2.DFS
DFS就是递归,我们要用递归的方式将一颗树序列化和反序列化,前序遍历根-左-右的关系利于我们序列化和反序列化时递归,递归不需要用栈储存节点遍历,直接用一个队列即可。
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
if (root == null) {
return "";
}
return dfs(root);
}
//前序遍历,特殊情况:null/X
public String dfs(TreeNode root) {
if(root==null){
return "X";
}
String left=dfs(root.left);
String right=dfs(root.right);
return root.val+","+left+","+right;
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
if(data==""){
return null;
}
//分割字符串,分割为多个节点,X对应null,数字对应普通节点
String[]arr=data.split(",");
Deque<String>queue=new ArrayDeque<>(Arrays.asList(arr));
int index=0;
return redfs(queue);
}
//前序遍历,特殊情况:null/X
TreeNode redfs(Deque<String>queue){
String str=queue.poll();
if(str.equals("X"))return null;
TreeNode root=new TreeNode(Integer.parseInt(str));
root.left=redfs(queue);
root.right=redfs(queue);
return root;
}
}