99. 恢复二叉搜索树
二叉搜索树中的两个节点被错误地交换。
请在不改变其结构的情况下,恢复这棵树。
思路
显式中序遍历
二叉搜索树中序遍历为有序数组,如[1, 2, 3, 4, 5, 6],交换其中不相邻任两个,变成[1, 6, 3, 4, 5, 2]会有两个逆序对[6, 3]和[5, 2],可以看出交换位置为前一逆序对的前一个元素和后一逆序对的后一元素。相邻的话只有一个逆序对。
中序遍历然后存储在数组中,在数组中找出要交换的元素值x,y,然后再遍历树修改值即可。
/**
* 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 inorder(TreeNode* root, vector<int>& ans) {
//中序便利
if(root != nullptr) {
inorder(root -> left, ans);
ans.push_back(root -> val);
inorder(root -> right, ans);
}
}
pair<int, int> findSwap(const vector<int>& ans) {
int n = ans.size();
int x = -1, y = -1;
//找出逆序对的位置
for(int i = 0; i < n - 1; i++) {
if(ans[i] > ans[i + 1]) {
y = ans[i + 1];
if(x == -1) {
x = ans[i];
}
else {
break;
}
}
}
return {x, y};
}
void recover(TreeNode* root, int count, int x, int y) {
if(root != nullptr) {
if(root -> val == x || root -> val == y) {
root -> val = root -> val == x? y : x;
if(--count == 0) {
return ;
}
}
recover(root -> left, count, x, y);
recover(root -> right, count, x, y);
}
}
void recoverTree(TreeNode* root) {
vector<int> ans;
inorder(root, ans);
pair<int, int> index = findSwap(ans);
recover(root, 2, index.first, index.second);
}
};
隐式中序遍历
其实就是省去ans数组的空间,注意到其实我们只需要比较当前访问节点和前一访问节点的大小信息,就可以知道了待交换节点了。使用迭代方法进行中序遍历,需要一个栈来作为辅助数组,保存前节点信息即可。
/**
* 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 == nullptr) {
return;
}
stack<TreeNode*> s;
TreeNode* x = nullptr;
TreeNode* y = nullptr;
TreeNode* pre = nullptr;
while(!s.empty() || root != nullptr) {
//栈辅助中序遍历
while(root != nullptr) {
s.push(root);
root = root -> left;
}
root = s.top();
s.pop();
if(pre != nullptr) {
if(pre -> val > root -> val) {
y = root;
if(x == nullptr) {
x = pre;
}
else {
break;
}
}
}
pre = root;
root = root -> right;
}
swap(x -> val, y -> val);
}
};
Morris中序遍历
Morris中序遍历法可以将非递归中序遍历空间复杂度降为O(1),但是时间复杂度会上升到O(2n)。
步骤(假定当前遍历到的节点为x):
- 1、当前节点有左孩子,寻找当前节点的前驱节点predecessor,也就是左子树里面最右节点。
– 如果最右节点的右孩子节点为空,令右孩子节点指向x,也就是predecessor -> right = x。
– 如果最右节点的右孩子节点为当前节点x,也就是predecessor -> right == x,证明已经遍历完毕当前节点x的左子树,恢复树的结构,也就是predecessor -> right = nullptr,然后令当前节点前进为右孩子节点,x = x -> right。 - 2、若当前节点没有左孩子节点,直接前进,x = x -> right。
Morris中序遍历的访问节点情况出现在当前节点没有左孩子节点时和当前节点的前驱节点指向当前节点时,前面一种情况说明该节点必须输出了(中序遍历的定义),后面一种情况说明当前节点已经被第二次访问,说明当前节点的左子树已经访问完毕了,需要输出当前节点了。
回到该题,所以我们只需在上述两种该输出情况中,比较pre节点和当前节点的值即可。
/**
* 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) {
TreeNode* x = nullptr, *y = nullptr, *pre = nullptr, *predecessor = nullptr;
while(root != nullptr) {
//当前节点有左子树
if(root -> left != nullptr) {
//需要左子树最右节点
predecessor = root -> left;
while(predecessor -> right != nullptr && predecessor -> right != root) {
predecessor = predecessor -> right;
}
//最右节点右孩子为空,设置指向当前节点,同时前进
if(predecessor -> right == nullptr) {
predecessor -> right = root;
root = root -> left;
}
//最右节点右孩子为当前节点,说明左子树访问完毕,输出当前节点,前进右孩子节点
else {
if(pre != nullptr) {
if(pre -> val > root -> val) {
y = root;
if(x == nullptr) {
x = pre;
}
}
}
pre = root;
predecessor -> right = nullptr;
root = root -> right;
}
}
//当前节点没有左孩子节点,输出当前节点,直接前进右孩子节点,
else {
if(pre != nullptr) {
if(pre -> val > root -> val) {
y = root;
if(x == nullptr) {
x = pre;
}
}
}
pre = root;
root = root -> right;
}
}
swap(x -> val, y -> val);
}
};