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。那麼如果把所有葉子結點的右空指標利用起來,在正式訪問它們之前把它們的右指標指向各自對應的後續節點。就可以解決“從樹上下去,上不來了”的問題。
-
在中序遞迴遍歷的演算法中,結構是這樣的
-
遞迴左孩子
-
訪問當前節點
-
遞迴右孩子
-
“上樹”發生在遞迴左孩子到訪問當前節點的過程中,而後兩步之間不會發生。
-
想要利用右鏈葉子的右指標,還必須要能夠區分該節點是否是右鏈葉子(該節點的右孩子是否真正是右孩子)。因為這兩種情況對右孩子的處理不一樣,如果是“上樹”的右孩子,那麼可以直接“訪問”右孩子,如果是普通的右孩子,那麼還需要再找該右孩子的前序節點。
-
如何區分?通過判斷節點的前驅結點的右孩子是否指向自己。
-
步驟(自己的總結)
-
找前驅結點(如果找到了,一定在左子樹右鏈尾,可以保證切換當前節點後可以“上樹”,如果沒有找到說明當前節點不存在左孩子,跳過此步)。如果右孩子為空,把前驅結點的右孩子設為當前節點,再進入左孩子。迴圈以上直到不存在左孩子(說明到了未問過的首節點);如果不為空,則設為空
-
訪問該節點。進入當前節點的右孩子;