前言
一题多解,才能打开格局和视野,多角度刨析问题,才能举一反三。
如:二叉搜索树如何展开成双向链表?
M1:通过中序遍历二叉树即可,把每个节点接入全局变量的链表尾指针。
M1-defect:需要一个成员变量来帮助。
那直接在中序遍历中可否操作?
key-problem:中序遍历不能做到随时拿到新的tail指针,则可通过对象数组方式来解决。
key-problem:中序遍历不借助tail指针或数组,是不能可能解决问题的。可否不用中序遍历解决?
刨析二叉搜索树的特点:
二叉搜索树特点,每个节点的前驱为其左子树的最右节点,每个节点的后继为其右子树的最左节点。所以可以返回一个Node数组存储子树的最左右节点即可。
一、二叉搜索树与双向链表
二、中序遍历
1、中序遍历+成员变量
//二叉搜索树与双向链表
public class TreeToDoublyList {
/*
target:按照二叉搜索树的节点顺序将节点以双向循环链表的方式串起来。
顺序性?通过中序遍历即可。
双向链表?通过记录pre节点+不断更新pre节点+pre 与 cur节点left、right操作。
循环?不断left找到头节点,中序遍历可以得到尾节点,然后设置以下left、right即可。
*/
public Node treeToDoublyList(Node root) {
if (null == root) return null;
//1-寻找起始节点。
Node head = null, cur = root;
while (cur.left != null) cur = cur.left;
head = cur;
//递归回溯来改变链的结构。
dfs(root, pre);
//双向循环
head.left = pre;
pre.right = head;
//返回头节点
return head;
}
Node pre = new Node();//传入的pre是一个地址,但是我们要更新的不是地址的值,而是pre本身的指向。
private void dfs(Node root, Node pre) {
if (root == null) return;
//回溯改变链的结构
dfs(root.left, pre);
pre.right = root;
root.left = pre;
pre = root;
dfs(root.right, pre);
}
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node() {
}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right) {
val = _val;
left = _left;
right = _right;
}
}
}
2、中序遍历+非成员数组
class TreeToDoublyList2 {
/*
target:按照二叉搜索树的节点顺序将节点以双向循环链表的方式串起来。
顺序性?通过中序遍历即可。
双向链表?通过记录pre节点+不断更新pre节点+pre 与 cur节点left、right操作。
循环?不断left找到头节点,中序遍历可以得到尾节点,然后设置以下left、right即可。
*/
public Node treeToDoublyList(Node root) {
if (null == root) return null;
//1-寻找起始节点。
Node head = null, cur = root;
while (cur.left != null) cur = cur.left;
head = cur;
Node[] pre = new Node[]{new Node()};//把tail指针当作一个值来更新。
//递归回溯来改变链的结构。
dfs(root, pre);
//双向循环
head.left = pre[0];
pre[0].right = head;
//返回头节点
return head;
}
private void dfs(Node root, Node[] pre) {
if (root == null) return;
//回溯改变链的结构
dfs(root.left, pre);
pre[0].right = root;
root.left = pre[0];
pre[0] = root;
dfs(root.right, pre);
}
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node() {
}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right) {
val = _val;
left = _left;
right = _right;
}
}
}
3、递归回溯–后序遍历处理每一个root
class TreeToDoublyList3 {
/*
target:按照二叉搜索树的节点顺序将节点以双向循环链表的方式串起来。
如何用局部变量的方式实现?
不用全局变量的一个问题就是:如何在中序遍历中拿到一个节点点的前后节点?
S:每个 root节点的前驱是左子树的最右孩子,root节点的后继是右子树的最左孩子。
所以,以回溯的方式返回子树的最左节点和最右节点。然后root就可以取它的前驱和后继。
*/
public Node treeToDoublyList(Node root) {
if (null == root) return null;
//root树最左和最右节点。
Node[] lt_rt = dfs(root);
//双向循环
lt_rt[0].left = lt_rt[1];
lt_rt[1].right = lt_rt[0];
//返回头节点
return lt_rt[0];
}
private Node[] dfs(Node root) {
//叶子节点的最左最右节点应该都是它自身
if (root == null) return null;
Node[] left = dfs(root.left);
Node[] right = dfs(root.right);
Node[] rs = new Node[]{root, root};
//叶子节点的最左最右都是它自己。
if (left == null && null == right) return rs;
//左子树不为空,应该把左子树的最右节点和root的left相关。
if (left != null) {
root.left = left[1];
left[1].right = root;
rs[0] = left[0];
}
if (right != null) {
root.right = right[0];
right[0].left = root;
rs[1] = right[1];
}
return rs;
}
// Definition for a Node.
class Node {
public int val;
public Node left;
public Node right;
public Node() {
}
public Node(int _val) {
val = _val;
}
public Node(int _val, Node _left, Node _right) {
val = _val;
left = _left;
right = _right;
}
}
}
总结
1)根据二叉搜索树的特点,用后序遍历解决。
2)中序遍历 配合 给引用整个引用----引用数组方式。
3)递归回溯