题目
题目来源:leetcode 99:恢复二叉搜索树
二叉搜索树中的两个节点被错误地交换。
请在不改变其结构的情况下,恢复这棵树。
例:
输入: [1,3,null,null,2]
输出: [3,1,null,null,2]
输入: [3,1,4,null,null,2]
输出: [2,1,4,null,null,3]
进阶:
使用 O(n) 空间复杂度的解法很容易实现。
你能想出一个只使用常数空间的解决方案吗?
解题思路
中序遍历正常的二叉搜索树得到的是一个递增序列。
最简单的思路就是,使用一个数组保存该树的中序遍历序列。然后对数组排序,最后再中序遍历一遍该树,依次把数组中的数值写到树的节点里。该方法的空间复杂度是O(N)
。
要在常数空间复杂度内解决该问题,首先得实现一种既不使用额外的数据结构、又不用递归的中序遍历树的算法。
Morris遍历
Morris
遍历不使用栈或递归,而是利用树中叶节点的右孩子指针模拟栈的功能。该方法能在常数空间内完成对二叉树的遍历,遍历的过程如下:
(1)
刚开始,root
指向根节点。
(2)
如果root
的左孩子不为空,则找到root->left
的最右孩子节点tmp
。
a.
如果tmp
的右孩子为空,说明root
是第一次遍历到当前节点的,将tmp
的右孩子指向root
(这是Morris
算法的灵魂所在,这个指向root
的指针就是用来模拟栈的功能的),并将root
左移。
b.
如果tmp
的右孩子是root
,说明当前是root
第二次遍历到当前节点(root
左边的节点都已经被遍历过了),于是将tmp
的右孩子恢复为nullptr
,root
右移。
(3)
如果root
的左孩子为空,则root
右移。
(4)
root
为空时,遍历结束。
中序遍历的结果取(2)b
和(3)
遇到的节点,详情见代码实现。
遍历的演示过程如下图所示:
寻找逆序位置
题目中已说明两个节点被错误地交换,因此只需做一次交换操作就可以保证二叉搜索树有序。
在中序遍历的过程中,将逆序的节点地址保存到一个数组vt
里。最终vt
的大小可能是2
,也可能是4
,不会有别的情况。
如果数组大小为2
,则交换vt[0]
和vt[1]
中的数值;否则交换vt[0]
和vt[3]
中的数值。
代码实现(C++)
O(N)空间复杂度:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
void recoverTree(TreeNode* root) {
vector<int> vi;