请在不改变树的结构的情况下恢复这棵树。
备注;
用O(n)的空间解决这个问题的方法太暴力了,你能设计一个常数级空间复杂度的算法么?
输入: [1,3,null,null,2]
1 / 3 \ 2
输出: [3,1,null,null,2]
3 / 1 \ 2
一般都会用递归或者是用栈来模拟,空间复杂必然不是O(1)的,最佳的解决方案是 Morris中序遍历。 /** * Definition for binary tree * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: // Could you devise a constant space solution? // Morris中序遍历, 时间复杂度O(n), 空间复杂度O(1) // ref: http://blog.csdn.net/shoulinjun/article/details/19051503 void recoverTree(TreeNode *root) { pair exchange; TreeNode* pre = nullptr, *cur = root; while(cur != nullptr) { // 不能继续往左走了 if(cur->left == nullptr) { // 访问节点, 比较节点大小 detect(exchange, pre, cur); pre = cur; cur = cur->right; } else { // 找到当前节点的前驱节点 auto node = cur->left; while(node->right != nullptr && node->right != cur) node = node->right; // 如果前驱节点的右子树为空, 则建立索引(指向其后继节点) if(node->right == nullptr) { node->right = cur; cur = cur->left; } // 已经线索化了 else { // 访问节点, 比较节点大小 detect(exchange, pre, cur); node->right = nullptr; // 消除线索 pre = cur; // 访问下一个节点 cur = cur->right; } } } swap(exchange.first->val, exchange.second->val); // 交换 } // 保存逆序节点 void detect(pair &exchange, TreeNode* pre, TreeNode* cur) { if(pre != nullptr && pre->val > cur->val) { if(exchange.first == nullptr) exchange.first = pre; // 保存那个逆序的第一个节点 exchange.second = cur; // 保存那个逆序的第二个节点 } } };
// 使用O(n)的空间存储中序遍历的值,重排后的正确的值赋给对应的节点 class Solution { public: void Inorder(TreeNode *root,vector<int> &vec,vector<TreeNode *> &node) { if(!root) return; Inorder(root->left,vec,node); vec.push_back(root->val); node.push_back(root); Inorder(root->right,vec,node); } void recoverTree(TreeNode *root) { vector<int> vec; vector<TreeNode *> node; Inorder(root,vec,node); sort(vec.begin(),vec.end()); for(int i=0;i<vec.size();i++) node[i]->val = vec[i]; } }; /// 网上解法2,使用中序遍历 TreeNode *pre; TreeNode *first; TreeNode *second; void recoverTree(TreeNode *root) { pre = NULL; first = NULL; second = NULL; inorder(root); if (first && second) swap(first->val, second->val); } void inorder(TreeNode *root) { if (!root) return; inorder(root->left); if(pre && pre->val > root->val) { if(!first) first =pre; second = root; } pre = root; inorder(root->right); }
//节点交换分两种情况,一种是相邻节点,这种交换不破坏两节点与之外的大小性,只破坏 //两节点之间大小性。另一种是不相邻,这种破坏两节点分别与两边的节点。 //因此在第一次搜到一个不满足的节点后,要将第二个节点也赋指针,以考虑第一种情况 class Solution { public: TreeNode *pre,*a,*b; void recoverTree(TreeNode *root) { pre=a=b=NULL; dfs(root); if(a&&b) swap(a->val,b->val); } void dfs(TreeNode *root){ if(!root) return; dfs(root->left); if(pre&&pre->val>root->val){ if(!a) a=pre; b=root; } pre=root; dfs(root->right); } };
//感觉系统有点抽,第一次没通过,后来都通过了。中序遍历的应用 //firstElem第一个被交换的element,secondElem被交换的element TreeNode firstElem = null, secondElem = null; //把上一个节点初始化为最小值 TreeNode preElem = new TreeNode(Integer.MIN_VALUE); public void recoverTree(TreeNode root) { // 中序遍历 inOrderTraverse(root); int tmp = firstElem.val; firstElem.val = secondElem.val; secondElem.val = tmp; } private void inOrderTraverse(TreeNode root) { if (root == null) return; inOrderTraverse(root.left); // 1234567到1264537 if (firstElem == null && preElem.val >= root.val) { firstElem = preElem; } if (firstElem != null && preElem.val >= root.val) { secondElem = root; } preElem = root; inOrderTraverse(root.right); }
//满足空间复杂度为常数 //思路:设定两个指针用来指向两处错误处和一个pre指针指向中序遍历的前一个结点。 public class Solution { TreeNode pre,mistake1,mistake2; public void recoverTree(TreeNode root) { inTraversal(root); mistake1.val=mistake2.val^mistake1.val^(mistake2.val=mistake1.val); } public void inTraversal(TreeNode root){ if(root==null) return ; inTraversal(root.left); if(pre!=null&&pre.val>root.val){ if(mistake1==null){ mistake1=pre; mistake2=root; }else mistake2=root; } pre=root; inTraversal(root.right); } }
import java.util.*; public class Solution { public void recoverTree(TreeNode root) { List<TreeNode> list = new ArrayList<>(); inorder(root, list); TreeNode first = null, second = null; for (int i = 0; i < list.size() - 1; i ++) { if(list.get(i).val > list.get(i + 1).val) { first = list.get(i); break; } } for (int i = list.size() - 1; i > 0; i --) { if(list.get(i).val < list.get(i - 1).val) { second = list.get(i); break; } } first.val = second.val ^ first.val ^ (second.val = first.val); } public static void inorder(TreeNode root, List<TreeNode> list) { if(root != null) { inorder(root.left, list); list.add(root); inorder(root.right, list); } } }
class Solution { public: TreeNode *pre,*a,*b; void recoverTree(TreeNode *root) { pre = a = b = NULL; DFS(root); if(a && b) swap(a->val, b->val); } void DFS(TreeNode *root) { if(root == NULL) return; DFS(root->left); if(pre && pre->val > root->val) { if(a == NULL) a = pre; b = root; } pre = root; DFS(root->right); } };
class Solution { public: TreeNode *la = nullptr; // 中序 TreeNode *lb = nullptr, *lc = nullptr; void dfs(TreeNode* root) { if (root == nullptr) return; if (root->left != nullptr) dfs(root->left); if (la != nullptr && la->val >= root->val) { if (lb == nullptr) lb = la, lc = root; else lc = root; } la = root; if (root->right != nullptr) dfs(root->right); } void recoverTree(TreeNode* root) { dfs(root); swap(lb->val, lc->val); } };
/* * 目的:修复BST * 方法1:中序遍历,找到错误位置,交换过来,空间复杂度O(n) * 方法2:中序遍历,找到错误结点,交换过来,空间复杂度O(n),与方法一大同小异 * 方法3:Morris中序遍历 */ //方法一: void inorder(TreeNoderoot, vector<TreeNode*>&pre){ if (root == nullptr) return; inorder(root->left, pre); pre.push_back(root); inorder(root->right, pre); } void recoverTree(TreeNode *root) { if (root==nullptr) return; vector<TreeNode*>pre; inorder(root, pre); vector<int>vec; for (int i = 0; i < pre.size();++i) { if (i == 0) { if (pre[i]->val>pre[i+1]->val) { vec.push_back(i); } } else if (i == pre.size()-1) { if (pre[i]->val<pre[i-1]->val) { vec.push_back(i); } } else if (pre[i]->val<pre[i-1]->val|| pre[i]->val>pre[i+1]->val) { vec.push_back(i); } } swap(pre[vec[0]]->val,pre[vec[vec.size()-1]]->val); } /*方法二: * 节点交换分两种情况, * 一种是相邻节点,这种交换不破坏两节点与之外的大小性,只破坏两节点之间大小性。 * 另一种是不相邻,这种破坏两节点分别与两边的节点。因此在第一次搜到一个不满足的节点后,要将第二个节点也赋指针,以考虑第一种情况 */ void inorder(TreeNode*root, TreeNode**pre, TreeNode**first, TreeNode**second){ if (root==nullptr) return; inorder(root->left, pre, first, second); if (*pre!=nullptr && (*pre)->val>root->val){ if (*first==nullptr){ *first = *pre; } *second = root; } *pre = root; inorder(root->right, pre, first, second); } void recoverTree(TreeNode *root) { if (root==nullptr) return; TreeNode*first = nullptr,*second = nullptr, *pre = nullptr; inorder(root, &pre, &first, &second); if (first!=nullptr && second !=nullptr) swap(first->val,second->val); } //方法三:Morris中序遍历,时间复杂度O(n),空间复杂度O(1) void detect(TreeNode*cur, TreeNode**pre, TreeNode**first, TreeNode**second){ if (*pre!=nullptr && (*pre)->val>cur->val){ if (*first==nullptr) *first = *pre; *second = cur; } *pre = cur; } void recoverTree(TreeNode *root) { if (root==nullptr) return; TreeNode*first= nullptr,*second=nullptr,*temp=nullptr,*pre=nullptr; while(root){ if (root->left ==nullptr){ detect(root, &pre, &first, &second); root = root->right; } else{ temp = root->left; while(temp->right!= nullptr && temp->right !=root){ temp = temp->right; } if (temp->right == nullptr){ temp->right = root; root = root->left; } else{ detect(root, &pre, &first, &second); temp->right = nullptr; root = root->right; } } } if (first && second) swap(first->val, second->val); }
//主要思想还是根据BST中序遍历的有序性来解决 //1、在中序遍历时记录前一个元素,即左相邻元素和当前元素相比较(这里统一使用左相邻元素和当前元素相比较), //如果左相邻元素大于当前元素则意味着这里出现了逆序。2、考虑如果在一排有序的元素中,有两个元素交换了位置, //则必然在较小位置上出现了较大的元素(即左相邻元素大于当前元素),而与其交换的元素必然是左相邻元素大于 //当前元素中的当前元素,此时还要考虑在递归中先找到位置较小的元素位置,再找位置较大的元素的位置。 class Solution { public: TreeNode *elemFirst=NULL,*elemSecond=NULL; TreeNode *elemPre=NULL; void dfsSelect(TreeNode *root) { if(root==NULL) return; dfsSelect(root->left); if(elemPre != NULL) { if(elemFirst==NULL && elemPre->val>root->val) { elemFirst = elemPre; } if(elemFirst!=NULL && elemPre->val>root->val) { elemSecond = root; } } elemPre = root; dfsSelect(root->right); } void recoverTree(TreeNode *root) { dfsSelect(root); if(elemFirst && elemSecond) { int tmp = elemFirst->val; elemFirst->val = elemSecond->val; elemSecond->val = tmp; } } };
//中序遍历,有两种情况,一种是相邻的交换,这种情况下,则只会有一个相邻的下降,将这个下降对交换就可 //如果不是的话就有两个下降对,将第一个下降对的前面和第二个下降对的后面交换即可 public class Solution { public void recoverTree(TreeNode root) { TreeNode[] result = new TreeNode[2] ; recover(root,null,result) ; int tmp = result[0].val ; result[0].val = result[1].val ; result[1].val = tmp ; } private TreeNode recover(TreeNode root,TreeNode parentPre,TreeNode[] result){ if(root == null){ return null ; } TreeNode myPre = recover(root.left,parentPre,result) ; myPre = myPre == null ? parentPre : myPre ; if(myPre != null){ if(myPre.val > root.val){ if(result[0] == null){ result[0] = myPre ; } result[1] = root ; } } TreeNode next = recover(root.right,root,result) ; if(next != null){ return next ; } return root ; } }
//注意题目要求O(1)的空间。采用中序遍历来读取二叉树的值肯定是按升序排序的。 //所以firstErrorNode等于大于下一个节点的值的节点。 //secondErrorNode等于小于上一个节点的值的节点。 public class Solution { private TreeNode firstErrorNode=null; private TreeNode secondErrorNode=null; private TreeNode lastNode=null; public void recoverTree(TreeNode root){ dealCore(root); int temp=secondErrorNode.val; secondErrorNode.val=firstErrorNode.val; firstErrorNode.val=temp; } private void dealCore(TreeNode root){ if(root==null)return; dealCore(root.left); if(lastNode!=null){ if(lastNode.val>root.val){ if(firstErrorNode==null){ firstErrorNode=lastNode; } secondErrorNode=root; } } lastNode=root; dealCore(root.right); } }
//只要用到递归空间复杂度至少是O(书的高度),所以要做到O(1)的空间复杂度,只有Morris中序遍历
public void recoverTree(TreeNode root) { if(root==null) return; TreeNode cur=root,m1=null,m2=null,last=null; while(cur!=null) { TreeNode pre=cur.left; if(pre!=null) { while(pre.right!=null&&pre.right!=cur) pre=pre.right; if(pre.right==null) { pre.right=cur; cur=cur.left; continue; } else pre.right=null; } if(last==null) last=cur; else { if(m1==null&&last.val>cur.val) { m1=last; m2=cur; } else if(m1!=null&&last.val>cur.val) m2=cur; last=cur; } cur=cur.right; } int t=m1.val; m1.val=m2.val; m2.val=t; }
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
void recoverTree(TreeNode* root) {
TreeNode* m1 = NULL;
TreeNode* m2 = NULL;
dfs(root, m1, m2, NULL);
if (m1==NULL) return;
int temp = m1->val;
m1->val = m2->val;
m2->val = temp;
return;
}
TreeNode* dfs(TreeNode* root, TreeNode* &m1, TreeNode* &m2, TreeNode* pre) {
if (root == NULL) return pre;
pre = dfs(root->left, m1, m2, pre);
if (pre != NULL && pre->val >= root->val) {
if (m1 == NULL) {
m1 = pre;
m2 = root;
}
else {
m2 = root;
}
}
return dfs(root->right, m1, m2, root);
}
};
/**
思路:二叉搜索树是否出现错误结点可以转换为中序遍历的前一个元素是否一直小于当前元素。总共分为下面两种情况:
{0,1}
{2,3,1}
如果出现前一个结点大于当前结点,将其记录下来即可。
参考 JacobGo!在本回答的答案,非常感谢!
* @author Rail
*
*/
public class RecoverSearchTree {
public static void main(String[] args) {
TreeNode root =new TreeNode(0);
root.left = new TreeNode(1);
RecoverSearchTree test = new RecoverSearchTree();
test.recoverTree(root);
}
private TreeNode firstErrorNode = null;
private TreeNode secondErrorNode = null;
private TreeNode preNode = new TreeNode(Integer.MIN_VALUE);
public void recoverTree(TreeNode root) {
inOrder(root);
int temp = firstErrorNode.val;
firstErrorNode.val = secondErrorNode.val;
secondErrorNode.val = temp;
}
private void inOrder(TreeNode root) {
if(root == null)
return;
inOrder(root.left);
if(preNode.val >= root.val ){
if(firstErrorNode == null){
firstErrorNode = preNode;
secondErrorNode = root;
}else{
secondErrorNode = root;
}
}
//为什么preNode = root可以记录上一个元素
//因为打印的话是中序遍历的顺序,如果当前执行第一次preNode保存root
//第二次执行到这句时就是当前root的上一个元素了
preNode = root;
inOrder(root.right);
}
}
// 有没有常数空间的解法啊,尝试了几遍出现问题 import java.util.ArrayList; import java.util.List; public class Solution { TreeNode pre, mistake1, mistake2; // O(n)空间方法就是中序遍历记录BST的所有节点,然后遍历两遍找到两个错误节点 public void recoverTree(TreeNode root) { if(root == null) return; List<TreeNode> list = new ArrayList<>(); inOrder(root, list); for(int i = 0; i < list.size() - 1; i++){ if(list.get(i).val > list.get(i + 1).val){ mistake1 = list.get(i); break; } } for(int i = list.size() - 1; i > 0; i--){ if(list.get(i).val < list.get(i - 1).val){ mistake2 = list.get(i); break; } } int val = mistake1.val; mistake1.val= mistake2.val; mistake2.val = val; } // O(n) public void inOrder(TreeNode root, List<TreeNode> list) { if(root == null) return; inOrder(root.left, list); list.add(root); inOrder(root.right, list); } }
}
中序遍历,查询,交换。
public void recoverTree(TreeNode root) { if (root==null)return; List<TreeNode> list=new ArrayList<TreeNode>(); Stack<TreeNode> stack=new Stack<TreeNode>(); while (root!=null||!stack.isEmpty()){ while (root!=null){ stack.push(root); root=root.left; } if (!stack.isEmpty()){ root=stack.pop(); list.add(root); root=root.right; } } TreeNode p=null; TreeNode q=null; for (int i=0;i<list.size()-1;i++){ if (list.get(i).val>list.get(i+1).val){ p=list.get(i); break; } } for (int j=list.size()-1;j>0;j--){ if (list.get(j).val<list.get(j-1).val){ q=list.get(j); break; } } if (p!=null && q!=null){ int tmp=q.val; q.val=p.val; p.val=tmp; } }
/** * Definition for binary tree * struct TreeNode { * int val; * TreeNode *left; * TreeNode *right; * TreeNode(int x) : val(x), left(NULL), right(NULL) {} * }; */ class Solution { public: void recoverTree(TreeNode *root) { stack<TreeNode*> s; TreeNode *pre = NULL, *q = root; TreeNode *first = NULL, *second = NULL; while(!s.empty() || q != NULL) { while(q != NULL) { s.push(q); q = q->left; } q = s.top();s.pop(); if(pre != NULL && q->val < pre->val) { // 1,2,3,4,5,6=>1,5,3,4,2,6 if(first == NULL) first = pre; second = q; } pre = q; q = q->right; } if(second != NULL) swap(first->val, second->val); } };
二叉搜索树的中序遍历是有序的,如果二叉搜索树中两个节点被互换了,那么其中序遍历中必定有两个节点“错位”,因此中序遍历是解题的关键。中序遍历本身不难,但是题目要求常数级别的空间复杂度,因此想到了线索二叉树。
总结下来两种思路:
指针的引用
这一特殊语法 线索二叉树
// // Created by jt on 2020/8/22. // #include <cstdio> using namespace std; class Solution { public: void recoverTree(TreeNode *root) { // pre保存上一个节点或者第一个错位节点 // chg保存第二个错位节点, detected用来指示是否检测到了第一个错位节点 TreeNode *pre = nullptr, *chg = nullptr, *cur = root; bool detected = false; while (cur) { if (cur->left) { // 如果存在左子树 TreeNode *p = cur->left; while (p->right && p->right != cur) p = p->right; if (p->right == cur) { // 如果已经建立过线索,更新pre和chg,然后消除线索 if (pre && pre->val > cur->val) {chg = cur; detected = true;} if (!pre || !detected) pre = cur; p->right = nullptr; cur = cur->right; } else { p->right = cur; cur = cur->left; } } else { // 如果不存在左子树,根据线索访问 if (pre && pre->val > cur->val) {chg = cur; detected = true;} if (!pre || !detected) pre = cur; cur = cur->right; } } if (pre && chg) { int tmp = chg->val; chg->val = pre->val; pre->val = tmp; } } };
递归实现
// // Created by jt on 2020/8/22. // #include <cstdio> using namespace std; class Solution { public: void recoverTree(TreeNode *root) { TreeNode *prev = nullptr, *current = nullptr; inOrder(root, prev, current); if (prev && current) { int tmp = prev->val; prev->val = current->val; current->val = tmp; } } void inOrder(TreeNode *root, TreeNode *&prev, TreeNode *¤t) { if (!root) return; if (root->left) inOrder(root->left, prev, current); if (prev && prev->val > root->val) current = root; if (!prev || !current) prev = root; if (root->right) inOrder(root->right, prev, current); return; } };