题意
给你二叉搜索树的根节点 root ,该树中 恰好 有两个节点的值被错误地交换。_请在不改变其结构的情况下,恢复这棵树_ 。
难度
中等
示例
示例 1:
输入:root=[1,3,null,null,2]
输出:[3,1,null,null,2]
解释:3不能是1的左孩子,因为3>1。交换1和3使二叉搜索树有效。
示例 2:
输入:root=[3,1,4,null,null,2]
输出:[2,1,4,null,null,3]
解释:2不能在3的右子树中,因为2<3。交换2和3使二叉搜索树有效。
分析
二叉搜索树一个重要的特点就是:中序遍历必然是一个单调递增的序列。
还记得中序遍历吧?
中序遍历是二叉树遍历的一种,它的遍历顺序为:左子树——>根节点——>右子树。
那为什么中序遍历必然是一个单调递增的序列呢?
因为中序遍历是先遍历左子树的,而左子树的值都比当前节点的小,然后是当前节点,再然后遍历右子树,而右子树的值又比当前节点的大。
很显然,如果有两个节点的位置恰好被错误的交换,那么中序遍历的时候,单调递增的特征就会被破坏掉,而此时,我们只需要找出这两个错误调换的节点,就能恢复二叉搜索树了。
具体怎么实现呢?
还是要依靠递归来帮忙
我们首先来中序遍历整个二叉搜索树。
接着从序列中找到nums[i] > nums[i + 1]的地方,因为正常的序列为递增序列,出现反常的地方,必然是交换的地方。
这样我们便可以找出交换过的节点,下一步,交换这个两个节点就能恢复原始的二叉搜索树了。
这次还是使用一次遍历,找到错误交换的两个节点,直接交换其位置即可。
一共三次遍历,第一次是中序遍历求序列,第二次是找调换位置,第三次是遍历二叉搜索树修改对应节点。
/**
*Definitionforabinarytreenode.
*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;
*}
*}
*/
class Solution{
private ArrayList<Integer> midTravel = new ArrayList<>();//用来存储中序遍历的结果
public void recoverTree(TreeNode root){
if(root==null){//如果根节点为空,则不需要恢复,直接返回
return;
}
middleTravel(root);//执行中序遍历
int[] nums = findPosition();//找到被错误交换的两个节点的值
checkTravel(root,nums[0],nums[1]);//再次遍历树,修复这两个节点的值
}
//中序遍历的辅助方法,用于读取节点的值
public void read(TreeNode pos){
midTravel.add(pos.val);
}
//中序遍历,获得整个二叉树的顺序
public void middleTravel(TreeNode pos){
if(pos==null){
return;
}
middleTravel(pos.left);//遍历左子树
read(pos);//读取当前节点
middleTravel(pos.right);//遍历右子树
}
//找出交换的元素,返回被错误交换的两个节点的值
public int[] findPosition(){
int siz = midTravel.size();//中序遍历结果的大小
int pos1 = -1, pos2 = -1;//初始化两个节点位置
for(int i=0;i<siz-1;i++){
//如果当前节点值大于下一个节点值,说明找到了一个被交换的节点
if(midTravel.get(i+1)<midTravel.get(i)){
pos2=i+1;
if(pos1==-1){
pos1=i;//记录第一个被交换的节点位置
}
}
}
return new int[] {midTravel.get(pos1),midTravel.get(pos2)};//返回两个被交换节点的值
}
//修改元素,修复树结构
public void checkTravel(TreeNode pos,int num1,int num2){
if(pos==null){
return;
}
//如果当前节点值是被交换的值之一,则将它修复为正确的值
if(pos.val==num1){
pos.val=num2;
}else if(pos.val==num2){
pos.val=num1;
}
//递归遍历左右子树
checkTravel(pos.left,num1,num2);
checkTravel(pos.right,num1,num2);
}
}
一共有三个主要的步骤:
- 中序遍历二叉搜索树,得到一个升序的数列。
- 使用中序遍历找到二叉搜索树中顺序错误的两个节点。在正常情况下,中序遍历二叉搜索树应该得到一个升序的数列。如果有两个数的顺序被交换了,那么就会出现两处顺序错误:第一处是较大的数与它后面的一个较小的数交换了位置,第二处是较小的数与它后面的一个较大的数交换了位置。
- 再次遍历树,修复这两个节点的值。遍历时检查每个节点的值,如果与被交换的两个值中的任何一个匹配,就将其替换为另一个。这样就能保证这两个节点的值被交换回来,从而恢复二叉搜索树的性质。
来看一下题解效率:
总结
AVL 树是二叉搜索树(BST)的一种,所以它继承了 BST 的所有特性。在 AVL 树中,对于任意节点,其左子树中的所有值都小于该节点的值,其右子树中的所有值都大于该节点的值。
本题并不要求我们处理或者维持 AVL 树的平衡,但在处理 BST 时理解其性质和可能的操作(如旋转)对于解决相关问题是有帮助的。
力扣链接:球极客挚爱的技术成长平台
一步一个脚印
不积跬步无以至千里,不积小流无以成江海。