99. 恢复二叉搜索树(困难) - 8月8日
题目
99. 恢复二叉搜索树
我的思路
如果不考虑空间复杂度,比较简单:中序遍历一次。因为树的某两个节点被调换了顺序,所以遍历得到的序列中也会有两个节点被调换顺序从而破坏了递增的顺序。
有两种情况:一种是两个原本就相邻(遍历产生的序列中相邻)的节点被调换顺序,第二种是原本不相邻的节点被调换顺序。两种情况分别会导致一次和两次违反正常升序的情况。
根据遍历得到的序列找到两个被交换的节点并,把它们的值swap即可。
以上做法的空间复杂度就是深搜中序遍历树的复杂度logn。
如果存在中序遍历树的空间复杂度比logn更低,那么就可以把最终解决方案的复杂度降到常数级别。
采用Morris遍历算法
/** * 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: TreeNode *preRoot; TreeNode *wrongRoot1; TreeNode *wrongRoot2; void visit(TreeNode *nodeptr) { std::cout << nodeptr->val << std::endl; if(preRoot!=NULL){ if(preRoot->val>nodeptr->val){ if(wrongRoot1==NULL){ wrongRoot1 = preRoot; } wrongRoot2 = nodeptr; } } preRoot = nodeptr; }; TreeNode *getPreccedor(TreeNode *root) { //两种情况:返回NULL(不存在左子树),返回其他(前驱结点) TreeNode *Pre; Pre = root->left; if(Pre==NULL)return NULL; while (Pre->right != NULL && Pre->right != root) { Pre = Pre->right; } return Pre; } void MorrisOrder(TreeNode *root) { TreeNode *Pre; while (root != NULL) { Pre = getPreccedor(root); if (Pre == NULL) { visit(root); root = root->right; } else if (Pre->right == root) { Pre->right = NULL; visit(root); root = root->right; } else { Pre->right = root; root = root->left; } } } void recoverTree(TreeNode* root) { preRoot = NULL; wrongRoot1 = NULL; wrongRoot2 = NULL; MorrisOrder(root); int ttt = wrongRoot1->val; wrongRoot1->val = wrongRoot2->val; wrongRoot2->val = ttt; } }; /* 如何判断被错误交换的两个节点是哪两个? 空间复杂度On的做法,个人感觉是,中序遍历一次,把节点按次序存储在一个队列中,判断队列中乱序的两个节点。交换这两个节点。 空间复杂度常数级别的做法,中序遍历的过程中就比较遍历顺序前后的顺序,如果出现错序,那么记录下来。在找到出现错误的两个节点,交换 */
我的实现
/** * 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: TreeNode *preRoot; TreeNode *wrongRoot1; TreeNode *wrongRoot2; void visit(TreeNode* root) { if(preRoot!=NULL) { if(root->val<preRoot->val) { if(wrongRoot1==NULL) { wrongRoot1 = preRoot; wrongRoot2 = root; } else { wrongRoot2 = root; } } } preRoot = root; } void mid_Search(TreeNode* root) { if(root!=NULL) { mid_Search(root->left); visit(root); mid_Search(root->right); } } TreeNode* getParent(TreeNode *root,TreeNode const * target) { if(root!=NULL) { if(root->left==target || root->right==target) { return root; } TreeNode * temp = getParent(root->left,target); if(temp!=NULL)return temp; else return getParent(root->right,target); } return NULL; } void recoverTree(TreeNode* root) { preRoot = NULL; wrongRoot1 = NULL; wrongRoot2 = NULL; mid_Search(root); int ttt = wrongRoot1->val; wrongRoot1->val = wrongRoot2->val; wrongRoot2->val = ttt; //cout<<wrongRoot1->val<<"\t"<<wrongRoot2->val<<endl; /* if(getParent(root,wrongRoot2)!=wrongRoot1&&getParent(root,wrongRoot1)!=wrongRoot2) { TreeNode * temp1 = new TreeNode(wrongRoot1->val); TreeNode * temp2 = new TreeNode(wrongRoot2->val); TreeNode * parent1,*parent2; parent1 = wrongRoot1!=root ? getParent(root,wrongRoot1) : NULL; parent2 = wrongRoot2!=root ? getParent(root,wrongRoot2) : NULL; //cout<<getParent(root,wrongRoot2)->val<<endl; //cout<<"check"<<wrongRoot1->val<<"\t"<<wrongRoot2->val<<endl; if(parent1!=NULL){ if(parent1->left==wrongRoot1) { parent1->left=temp2; }else{parent1->right=temp2;} } if(parent2!=NULL){ if(parent2->left==wrongRoot2) { parent2->left=temp1; }else{parent2->right=temp1;} } temp1->left = wrongRoot2->left; temp1->right = wrongRoot2->right; temp2->left = wrongRoot1->left; temp2->right = wrongRoot1->right; int ttt = wrongRoot1->val; wrongRoot1->val = wrongRoot2->val; wrongRoot2->val = ttt; } */ } }; /* 如何判断被错误交换的两个节点是哪两个? 空间复杂度On的做法,个人感觉是,中序遍历一次,把节点按次序存储在一个队列中,判断队列中乱序的两个节点。交换这两个节点。 空间复杂度常数级别的做法,中序遍历的过程中就比较遍历顺序前后的顺序,如果出现错序,那么记录下来。在找到出现错误的两个节点,交换 */
/** * 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: TreeNode *preRoot; TreeNode *wrongRoot1; TreeNode *wrongRoot2; void visit(TreeNode* root) { if(preRoot!=NULL) { if(root->val<preRoot->val) { if(wrongRoot1==NULL) { wrongRoot1 = preRoot; wrongRoot2 = root; } else { wrongRoot2 = root; } } } preRoot = root; } void mid_Search(TreeNode* root) { if(root!=NULL) { mid_Search(root->left); visit(root); mid_Search(root->right); } } TreeNode* getParent(TreeNode *root,TreeNode const * target) { if(root!=NULL) { if(root->left==target || root->right==target) { return root; } TreeNode * temp = getParent(root->left,target); if(temp!=NULL)return temp; else return getParent(root->right,target); } return NULL; } void recoverTree(TreeNode* root) { preRoot = NULL; wrongRoot1 = NULL; wrongRoot2 = NULL; mid_Search(root); int ttt = wrongRoot1->val; wrongRoot1->val = wrongRoot2->val; wrongRoot2->val = ttt; //cout<<wrongRoot1->val<<"\t"<<wrongRoot2->val<<endl; /* if(getParent(root,wrongRoot2)!=wrongRoot1&&getParent(root,wrongRoot1)!=wrongRoot2) { TreeNode * temp1 = new TreeNode(wrongRoot1->val); TreeNode * temp2 = new TreeNode(wrongRoot2->val); TreeNode * parent1,*parent2; parent1 = wrongRoot1!=root ? getParent(root,wrongRoot1) : NULL; parent2 = wrongRoot2!=root ? getParent(root,wrongRoot2) : NULL; //cout<<getParent(root,wrongRoot2)->val<<endl; //cout<<"check"<<wrongRoot1->val<<"\t"<<wrongRoot2->val<<endl; if(parent1!=NULL){ if(parent1->left==wrongRoot1) { parent1->left=temp2; }else{parent1->right=temp2;} } if(parent2!=NULL){ if(parent2->left==wrongRoot2) { parent2->left=temp1; }else{parent2->right=temp1;} } temp1->left = wrongRoot2->left; temp1->right = wrongRoot2->right; temp2->left = wrongRoot1->left; temp2->right = wrongRoot1->right; int ttt = wrongRoot1->val; wrongRoot1->val = wrongRoot2->val; wrongRoot2->val = ttt; } */ } }; /* 如何判断被错误交换的两个节点是哪两个? 空间复杂度On的做法,个人感觉是,中序遍历一次,把节点按次序存储在一个队列中,判断队列中乱序的两个节点。交换这两个节点。 空间复杂度常数级别的做法,中序遍历的过程中就比较遍历顺序前后的顺序,如果出现错序,那么记录下来。在找到出现错误的两个节点,交换 */
拓展学习
Morris遍历算法,总结数据结构在笔记《树》中。
Morris遍历算法
https://www.jianshu.com/p/484f587c967c
-
特点
-
中序遍历
-
空间复杂度为1,常数
-
核心思想总结
-
其他方法,使用栈或者递归,均需要至少logn的空间复杂度,原因是访问到某个根节点在左子树的前序节点后,要记录该根节点的指针,否则访问“从树上下去,上不来了”。
-
中序遍历中,“上树”的情形可以统一如下:从节点A的左孩子的右链叶子,上到节点A。那么如果把所有叶子结点的右空指针利用起来,在正式访问它们之前把它们的右指针指向各自对应的后续节点。就可以解决“从树上下去,上不来了”的问题。
-
在中序递归遍历的算法中,结构是这样的
-
递归左孩子
-
访问当前节点
-
递归右孩子
-
“上树”发生在递归左孩子到访问当前节点的过程中,而后两步之间不会发生。
-
想要利用右链叶子的右指针,还必须要能够区分该节点是否是右链叶子(该节点的右孩子是否真正是右孩子)。因为这两种情况对右孩子的处理不一样,如果是“上树”的右孩子,那么可以直接“访问”右孩子,如果是普通的右孩子,那么还需要再找该右孩子的前序节点。
-
如何区分?通过判断节点的前驱结点的右孩子是否指向自己。
-
步骤(自己的总结)
-
找前驱结点(如果找到了,一定在左子树右链尾,可以保证切换当前节点后可以“上树”,如果没有找到说明当前节点不存在左孩子,跳过此步)。如果右孩子为空,把前驱结点的右孩子设为当前节点,再进入左孩子。循环以上直到不存在左孩子(说明到了未问过的首节点);如果不为空,则设为空
-
访问该节点。进入当前节点的右孩子;
posted on 2020-08-09 17:32 BoysCryToo 阅读(128) 评论(0) 编辑 收藏 举报