题目
题目来源: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;
dfs1(root, vi);
sort(vi.begin(), vi.end());
int idx = 0;
dfs2(root, vi, idx);
}
void dfs1(TreeNode *root, vector<int> &vi) {
if (!root) {
return;
}
dfs1(root->left, vi);
vi.push_back(root->val);
dfs1(root->right, vi);
}
void dfs2(TreeNode *root, const vector<int> &vi, int &idx) {
if (!root) {
return;
}
dfs2(root->left, vi, idx);
root->val = vi[idx++];
dfs2(root->right, vi, idx);
}
};
O(1)空间复杂度:
/**
* 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) {
if (!root) {
return;
}
vector<TreeNode*> vt = Traverse(root);
int n = vt.size();
if (n == 2) {
SwapNode(vt[0], vt[1]);
}
else if (n == 4) {
SwapNode(vt[0], vt[3]);
}
}
vector<TreeNode*> Traverse(TreeNode *root) {
vector<TreeNode*> ans;
TreeNode *pre = nullptr;
while (root) {
if (root->left) {
TreeNode *tmp = root->left;
while (tmp->right && tmp->right != root) {
tmp = tmp->right;
}
if (tmp->right == nullptr) { // 第一次遍历到root
tmp->right = root;
root = root->left;
}
else { // 第二次遍历到root
tmp->right = nullptr;
if (pre && pre->val > root->val) {
ans.push_back(pre);
ans.push_back(root);
}
pre = root;
root = root->right;
}
}
else {
if (pre && pre->val > root->val) {
ans.push_back(pre);
ans.push_back(root);
}
pre = root;
root = root->right;
}
}
return ans;
}
void SwapNode(TreeNode *t1, TreeNode *t2) {
int t = t1->val;
t1->val = t2->val;
t2->val = t;
}
};
本文介绍了一种在O(1)空间复杂度下解决LeetCode 99题的方法,即如何恢复二叉搜索树。通过Morris遍历来遍历二叉搜索树,找出逆序节点并进行交换,从而恢复树的正确顺序。详细阐述了Morris遍历的步骤,并给出了C++代码实现。



606

被折叠的 条评论
为什么被折叠?



