回溯核心
前言
一、案例
给你一棵二叉搜索树,请 按中序遍历 将其重新排列为一棵递增顺序搜索树,使树中最左边的节点成为树的根节点,并且每个节点没有左子节点,只有一个右子节点。
二、题解
package com.xhu.offer.offerII;
//展开二叉搜索树
public class IncreasingBST {
//回溯改变树结构,自底向上把每一棵子树当作一棵完整二叉排序树,拿到首节点和尾节点来改变树结构。首尾节点即最左最右节点,最后返回最左节点即可。
public TreeNode increasingBST(TreeNode root) {
//回溯改变树结构,通过首尾即最左最右节点来改变树结构
TreeNode[] res = order(root);
//返回最左节点,即首节点,也即新树的根节点。
return res[0];
}
private TreeNode[] order(TreeNode root) {
//情况1:左右孩子单边为空的情况,此时最左或最右节点应变为root节点,以返回null示意。
if (root == null) return null;
//情况2:叶子节点,特殊节点,返回特殊化,左右都返回root,统一改变树结构的操作。
if (root.left == null && root.right == null) return new TreeNode[]{root, root};
//拿到左右子树的首尾,为回溯做数据准备。
TreeNode[] left = order(root.left);
TreeNode[] right = order(root.right);
//对应情况1的无左孩子情况
if (left == null) {
root.right = right[0];
return new TreeNode[]{root, right[1]};
}
//对应情况2的无右孩子情况
if (right == null) {
left[1].right = root;
root.left = null;
return new TreeNode[]{left[0], root};
}
//左右孩子都有
left[1].right = root;
root.left = null;
root.right = right[0];
//左右子树和父节点合并,这个合并返回是回溯的象征操作,自底向上,需要返回左子树的左和右子树的右,即首尾。
return new TreeNode[]{left[0], right[1]};
}
//中序遍历本身的结果就是变为右子树的样子,所以记录前一个节点+中序改变树结构即可,用dummy节点来统一空树操作。
TreeNode dummyRoot = new TreeNode(), p = dummyRoot;
public TreeNode increasingBST2(TreeNode root) {
if (root == null) return dummyRoot.right;
increasingBST2(root.left);
p.right = root;
root.left = null;
p = root;
increasingBST2(root.right);
return dummyRoot.right;
}
//Definition for a binary tree node.
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode() {
}
TreeNode(int val) {
this.val = val;
}
TreeNode(int val, TreeNode left, TreeNode right) {
this.val = val;
this.left = left;
this.right = right;
}
}
}
总结
1)回溯对于二叉树非常重要,它的核心在于自底向上合并子树,特殊(叶子)节点合并特殊化。
2)二叉树+回溯+栈+单向链表+dummyNode,五种关系较紧密。
参考文献
[1] LeetCode 原题