每日算法总结——寻找二叉树最低公共祖先、在二叉树中找到一个节点的后继节点、二叉树的序列化和反序列化,折纸问题

一、寻找二叉树最低公共祖先

【题目】:给定二叉树的两个节点node1和node2,找到它们的最低公共祖先节点。

方法一、使用HashMap记录每个节点的父节点
  • 算法思路:首先进行一次遍历,将树中的全部节点都放入HashMap中,并记录它们对应的父节点,然后根据HashMap求得node1所有的祖先节点,并将其放入一个Set中,对node2同样向上查找,判断node2的每个祖先节点是否在Set中,在的话则该节点就是最低公共祖先节点。
/**
 * 使用HashMap记录每个节点的父节点
 */
public static Node lowestCommonAncestor1(Node head, Node node1, Node node2) {
    Map<Node, Node> fatherMap = new HashMap<>();
    // 记录每个节点的父节点
    fatherMap.put(head, head);
    process(head, fatherMap);
    Set<Node> set1 = new HashSet<>();
    Node cur = node1;
    while (cur != fatherMap.get(cur)) {
        set1.add(cur);
        cur = fatherMap.get(cur);
    }
    set1.add(head);
    cur = node2;
    while (cur != fatherMap.get(cur)){
        if (set1.contains(cur)) {
            return cur;
        }
        cur = fatherMap.get(cur);
    }
    // 只有根节点是公共祖先
    return head;
}

public static void process(Node head, Map<Node, Node> fatherMap) {
    if (head == null) {
        return;
    }
    fatherMap.put(head.left, head);
    fatherMap.put(head.right, head);
    process(head.left, fatherMap);
    process(head.right, fatherMap);
}
方法二、递归优化

递归优化后的方法,比较抽象🤯,如何理解?

public static Node lca(Node head, Node node1, Node node2) {
    if (head == null || head == node1 || head == node2) {
        return head;
    }
    Node left = lca(head.left, node1, node2);
    Node right = lca(head.right, node1, node2);
    if (left != null && right != null) {
        return head;
    }
    return left != null ? left : right;
}
  • 我们要找node1和node2的最低公共祖先,则可能的情况有以下几种:
    1. 该公共祖先就是node1或者node2。
    2. 公共祖先不是node1或node2,需要从node1、node2向上找才能找到。
  • 回到上面的代码,对于以head为根节点的子树,无非也就下面几种情况:
    1. head为node1/node2,则不管它下面的节点中有没有另一个node,head都认为自己 (node1/node2) 就是公共祖先,将自己返回🧐(可能自己不是公共祖先,这需要上面节点进行判断)。
    2. head不为node1/node2,则head需要根据左右两个子树的信息,来判断在它的下面是否有node1和node2的存在:
      • 如果left和right都返回null,说明head下面没有node1和node2的存在,自己也向上返回null,并且认为在自己这棵树上不存在node1和node2的公共子节点;😔
      • 如果left和right只有一个返回null,说明head下面的一个子树有node1/node2的存在(也有可能都存在),就把从该子树得到的节点返回,并且认为该节点就是node1和node2在自己这棵树上的最低公共祖先节点。😉
      • 如果left和right都不返回null,说明head的左右子树分别有node1/node2的存在,此时很明显,head自己就是node1和node2的公共祖先节点,于是将自己向上返回。😋
    3. head为null,啥都没有,啥都不知道,直接返回。🤡

二、在二叉树中找到一个节点的后继节点

【题目】现在有一种新的二叉树节点类型如下:

public class Node{
    public int value;
    public Node left;
    public Node right;
    public Node parent;
    
    public Node(int val) {
        value = val;
    }
}
  • 该结构比普通二叉树节点结构多了一个指向父节点的parent指针。
  • 假设有一棵Node类型的节点组成的二叉树,树中每个节点的parent指针都正确地指向自己的父节点,头节
    点的parent指向null。
  • 只给一个在二叉树中的某个节点node,请实现返回node的后继节点的函数。
  • 在二叉树的中序遍历的序列中,node的 下一个节点叫作node的后继节点。

什么是后继节点?

中序遍历的顺序,紧跟在节点A后面的节点B,称为节点A的后继节点


显然通过中序遍历,可以知道所有节点的后继节点,但题目中的Node结构给了父节点parent,要求某个节点的后继节点没必要把所有节点的后继节点都求出来。

  • 算法思想:根据中序遍历的顺序,对于节点node,可以有以下两种情况:
    1. node有右子树,则node的后继节点就是,右子树的最左节点
    2. node无右子树,则需要依据node.parent不断向上看,如果node不是node.parent的左孩子,则继续往上看(node = node.parent),…,如果最终判定到了node是node.parent的左孩子,则node.parent就是要求的后继节点。如果最终判定到了node.parent = node,则要求的后继节点为null(此时,node为二叉树的最右节点)。
/**
 * 在二叉树中找到一个节点的后继节点
 */
public static Node getSuccessorNode(Node node) {
    if (node == null) {
        return null;
    }
    if (node.right != null) {
        return getLeftMost(node.right);
    } else {
        // 无右子树
        Node parent = node.parent;
        // 当前节点是其父亲节点的右孩子
        while (parent != null && parent.left != node) {
            node = parent;
            parent = node.parent;
        }
        return parent;
    }
}

public static Node getLeftMost(Node node) {
    while (node.left != null) {
        node = node.left;
    }
    return node;
}

三、二叉树的序列化和反序列化

什么是二叉树的序列化和反序列化?

就是内存里的一棵树如何变成字符串形式,又如何从字符串形式变成内存里的树。

方式之一就是利用先序遍历,将一棵树按照先序遍历序列进行拼接,同时各个节点直接用_分割(随便什么都行),空节点用#表示

/**
 * 先序遍历顺序序列化树
 */
public static String serialByPre(Node root) {
    if (root == null) {
        return "#_";
    }
    String res = root.value + "_";
    res += serialByPre(root.left);
    res += serialByPre(root.right);
    return res;
}

/**
 * 先序遍历顺序进行树的反序列化
 */
public static Node reconByPre(String s) {
    String[] value = s.split("_");
    Queue<String> queue = new LinkedList<>(Arrays.asList(value));
    return reconPreOrder(queue);
}

public static Node reconPreOrder(Queue<String> queue) {
    String value = queue.poll();
    assert value != null;
    if (value.equals("#")) {
        return null;
    }
    Node root = new Node(Integer.parseInt(value));
    root.left = reconPreOrder(queue);
    root.right = reconPreOrder(queue);
    return root;
}

四、折纸问题

准备一较长的长方形纸条,拎着纸条的一头并让该头的某一面始终对着自己,开始第一次对折,在纸条中心出现一条凹折痕;第二次对折,纸条上下分别出现了凹折痕凸折痕,再次对折·······,当对折次数达到N时,从上到下打印所有折痕。

不难发现规律:N次对折时,会在N-1次对折折痕的上下两侧出现凹折痕和凸折痕,所以可将所有折痕看作是一完全二叉树的节点。

public static void printAllFolds(int n) {
    process(1, n, true);
}

/**
 * 递归过程,来到了某一个节点
 *
 * @param i    当前节点的层数
 * @param n    总层数,即一共折了多少次
 * @param down 当前节点是凹节点还是凸节点,true为凹,false为凸。
 */
public static void process(int i, int n, Boolean down) {
    if (i > n) {
        return;
    }
    process(i + 1, n, true);
    System.out.println(down ? "凹" : "凸");
    process(i + 1, n, false);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值