“二叉樹”相關題目
/******************************************/
二叉樹的問題,一定要明白到底應該深度優先(前中後序)還是廣度優先(層序遍歷)
最基本的遍歷方式:深度優先和廣度優先
深度優先:前、中、後序(遞迴法和迭代法均可)
廣度優先:層次遍歷(迭代法)
棧其實就是遞迴的一種實現結構,也就是說前中後序遍歷的邏輯其實都是可以藉助棧使用非遞迴的方式來實現的;
廣度優先遍歷(層序遍歷)的實現一般使用佇列來實現,這也是佇列先進先出的特點所決定的,因為需要先進先出的結構,才能一層一層的來遍歷二叉樹。力扣102題
遞迴演算法的三要素:
-
「確定遞迴函式的引數和返回值:」確定哪些引數是遞迴的過程中需要處理的,那麼就在遞迴函式里加上這個引數, 並且還要明確每次遞迴的返回值是什麼進而確定遞迴函式的返回型別。
-
「確定終止條件:」寫完了遞迴演算法, 執行的時候,經常會遇到棧溢位的錯誤,就是沒寫終止條件或者終止條件寫的不對,作業系統也是用一個棧的結構來儲存每一層遞迴的資訊,如果遞迴沒有終止,作業系統的記憶體棧必然就會溢位。
-
「確定單層遞迴的邏輯:」確定每一層遞迴需要處理的資訊。在這裡也就會重複呼叫自己來實現遞迴的過程
二叉樹節點的定義框架:
struct TreeNode { int val; TreeNode *left; TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) {} };
二叉樹相關的題目永遠都逃不開樹的遞迴遍歷框架:
/*二叉樹的遍歷框架*/ void traverse(TreeNode root) { //前序遍歷:先訪問根節點,再前序訪問左子樹,再訪問右子樹 traverse(root->left); //中序遍歷:先中序訪問左子樹,再訪問根節點,再訪問右子樹 traverse(root->right); //後續遍歷:先後續訪問左子樹,再訪問右子樹,再訪問根節點 }
快速排序其實就是二叉樹的前序遍歷。
快速排序的邏輯是,需要對一個數組進行排序,我知道陣列的首地址,知道首元素和最後一個元素的下標,然後我就先找一個分界點p,通過交換元素使得分界點p左邊的值都比p小,右邊的都比p大,然後遞迴的去這樣做,最後整個陣列就被排序了。這裡需要知道的是,分界點p是陣列的下標,怎麼得到呢,我們在執行“分”的這個操作的時候,是先選取了一個基準值,一般選的是陣列最後一個元素,當然優化的方式更好是隨機選擇的,選好基準值後,通過迴圈判斷將比基準值小的都往左邊放,最後將基準值放到數組合適的位置,這個位置的下標就是分界點p了。得到p之後,遞迴的對p左邊的子陣列和p右邊的子陣列進行排序,傳入的引數是陣列的首地址,子陣列的首元素和尾元素下標,這個根據p得到。
程式碼框架如下:
void sort(vector<int>& nums, int lo, int hi) { /*前序遍歷位置*/ //通過交換元素構建分界點p int p = partition(nums, lo, hi); /***********************/ sort(nums, lo, p - 1); sort(nums, p + 1, hi); }
歸併排序其實就是二叉樹的後序遍歷。
歸併排序的邏輯是,需要對一個數組進行排序,我知道陣列的首地址,知道首元素和最後一個元素的下標,然後我將陣列一分為二,分別對左右子陣列進行排序,所以應該先求一個mid,以便於將陣列通過下標分開,然後遞迴進行左右子陣列排序,最後,將這兩個有序陣列進行合併,整個陣列就排好了,合併的時候,是將兩個有序的子數組合並起來,需要將兩個陣列從頭開始比較元素,構建一個新的容器暫存有序的陣列,一直保持將小的先放進去的原則,放完之後,肯定有一個子陣列完全放進去了,還有一個是沒有的,所以需要進行判斷,將剩下的放進去。
程式碼框架如下:
void sort(vector<int>& nums, int lo, int hi) { int mid = (lo + hi) / 2; sort(nums, lo, mid - 1); sort(nums, mid + 1, hi); /*後序遍歷位置*/ //合併兩個排好序的子陣列 merge(nums, lo, mid, hi); /*****************/ }
寫遞迴演算法的祕訣:關鍵是要明確函式的定義是什麼,然後相信這個定義,利用這個定義推到最終的結果,千萬不能跳進遞迴的細節。
關於二叉樹的題目基本都是用遞迴很容易解決,問題是,你要搞清楚,這個題目當中,相當於讓每個節點幹什麼事情。
二叉樹的前序遍歷:迭代和遞迴
class Solution { public: //vector<int> result;//遞迴的話定義在這裡 vector<int> preorderTraversal(TreeNode* root) { //遞迴方式 /* if(root == nullptr) return {}; result.push_back(root->val); preorderTraversal(root->left); preorderTraversal(root->right); return result; */ //當然可以使用迭代解法,因為遞迴本身就是用棧來實現的,可以通過棧來迭代操作 //但是要注意棧的特性是後入先出,前序的話,就是先放入根節點賦值操作彈出,再放入右節點、左節點,再彈出,這樣左節點就會先出,先賦值操作,就是前序了 stack<TreeNode*> sta; vector<int> result; sta.push(root); while(!sta.empty()) { int size = sta.size(); for(int i=0; i<size; i++) { TreeNode* node = sta.top(); sta.pop(); result.push_back(node->val); if(node->right) sta.push(node->right); if(node->left) sta.push(node->left); } } return result; } };
二叉樹的後序遍歷:迭代和遞迴
/** * 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: //vector<int> result;//遞迴解法定義在這裡 vector<int> postorderTraversal(TreeNode* root) { /* if(root == nullptr) return {}; postorderTraversal(root->left); postorderTraversal(root->right); result.push_back(root->val); return result; */ //本題還可以採用迭代解法,因為遞迴就是用棧來實現的 //考慮實現的過程 //後序遍歷是左右中的順序,但是我們在迭代的時候肯定會先訪問根節點,也就是中間的節點,所以考慮先訪問和處理中間節點,再處理右節點,再處理左邊節點,最後將結果翻轉就行了 stack<TreeNode*> sta; vector<int> result; sta.push(root); while(!sta.empty()) { int size = sta.size(); for(int i=0; i<size; i++) { TreeNode* node = sta.top(); sta.pop(); result.push_back(node->val); if(node->left) sta.push(node->left); if(node->right) sta.push(node->right); } } reverse(result.begin(), result.end()); return result; } };
二叉樹的中序遍歷
/** * 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: //vector<int>result;//遞迴寫法這裡定義 vector<int> inorderTraversal(TreeNode* root) { /*遞迴解法 if(root == nullptr) return {}; inorderTraversal(root->left); result.push_back(root->val); inorderTraversal(root->right); return result; */ //還能採用迭代解法,用棧來解決,因為遞迴本身就是用棧來實現的,因此是完全行得通的 //中序的順序是左中右,那出棧的時候,處理的順序肯定是右中左 //搞清楚訪問和處理的概念 //訪問:將節點入棧 //處理:將節點的值放入結果集 //中序的訪問和處理的順序是不一樣的,所以要藉助指標進行訪問,也就是將節點放入棧中,用棧來做處理,也就是放入結果集 vector<int> result; stack<TreeNode*> sta; TreeNode* cur = root; while(cur != nullptr || !sta.empty()) { if(cur != nullptr) {//指標用來訪問節點,訪問到左邊最底層的時候,指標和要開始處理的位置就一樣了 sta.push(cur);//將訪問的節點放進棧 cur = cur->left;//最左的子節點最後放進去,所以會先出棧 左 } else { cur = sta.top(); sta.pop(); result.push_back(cur->val); //中 cur = cur->right; //右 } } } };
/** * 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: vector<vector<int>> levelOrder(TreeNode* root) { queue<TreeNode*> que;//建立一個佇列,層序遍歷樹的需要用佇列來實現,佇列中是二叉樹的節點 if(root != nullptr) que.push(root);//如果頭結點不為空的話,先將頭結點放到佇列中,因為頭結點也就是第一行,只有這一個元素,所以直接放進去 vector<vector<int>> result;//定義返回值,返回的是一個二維陣列 while(!que.empty()) { int size = que.size();//同一行可能不止一個元素,要迴圈都進行遍歷,又因為下面要進行pop操作,que.size()是一個變化的值,所以這裡儲存數量 vector<int> vec;//用於臨時儲存每一行的節點值,最後統一存入返回的二維陣列中 for(int i=0; i<size; i++) { TreeNode* node = que.front(); que.pop();// vec.push_back(node->val); if(node->left) que.push(node->left);//將這個節點的左右子節點放入佇列中 if(node->right) que.push(node->right); } result.push_back(vec); } return result; } };
掌握了層序遍歷的模板,別的題只要稍微改動幾行程式碼就可以解決了。
遇到二叉樹的題目,一定要想一想到底是用深度優先遍歷還是廣度優先遍歷,到底使用迭代法還是用遞迴法。
遇到求二叉樹節點的數量以及求二叉樹的深度相關的題目,使用迭代和遞迴方法均可,關鍵是要自動向下對問題進行分析。
226.翻轉二叉樹
/** * 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: TreeNode* invertTree(TreeNode* root) { //通過分析,得到了題意是:將每個節點的左右子節點進行翻轉就可以了,前序和後序的方式都行 //不能使用中序的原因是某些節點可能會被翻轉兩次 /* if(root == NULL) return root; swap(root->left, root->right); invertTree(root->left); invertTree(root->right); return root; */ //以上是遞迴寫法,這道題還可以有迭代寫法,反正知道了題意就是將每個節點的左右子節點進行互換就可以了,可以遍歷每個節點進行操作 /* if(root == NULL) return root; stack<TreeNode*> st; st.push(root); while(!st.empty()) { TreeNode* node = st.top(); st.pop(); swap(node->left, node->right); if(node->right) st.push(node->right); if(node->left) st.push(node->left); } return root; */ //以上是深度優先遍歷當中的前序迭代寫法,還可以採用層序遍歷 if(root == NULL) return root; queue<TreeNode*> que; que.push(root); while(!que.empty()) { int size = que.size(); for(int i=0; i<size; i++) { TreeNode* node = que.front(); que.pop(); swap(node->left, node->right); if(node->left) que.push(node->left); if(node->right) que.push(node->right); } } return root; } };
114.二叉樹展開為連結串列
/** * 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 flatten(TreeNode* root) { //後序遍歷:在還沒操作節點右子樹前,不能破壞該節點的右子樹指向 if(root == nullptr) return; flatten(root->left); flatten(root->right); //左右子樹已經被拉平成一條連結串列,需要將左子樹變成右子樹,將原來的右子樹節點接到後面 TreeNode* left = root->left; TreeNode* right = root->right; root->left = nullptr; root->right = left; TreeNode* p = root; while(p->right != nullptr) { p = p->right; } p->right = right; } };
/* // Definition for a Node. class Node { public: int val; Node* left; Node* right; Node* next; Node() : val(0), left(NULL), right(NULL), next(NULL) {} Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {} Node(int _val, Node* _left, Node* _right, Node* _next) : val(_val), left(_left), right(_right), next(_next) {} }; */ class Solution { public: Node* connect(Node* root) { if(root == nullptr) return nullptr; connectTwoNode(root->left, root->right); return root; } void connectTwoNode(Node* node1, Node* node2) { if(node1 == nullptr || node2 == nullptr) return;//不操作 node1->next = node2; connectTwoNode(node1->left, node1->right); connectTwoNode(node2->left, node2->right); connectTwoNode(node1->right, node2->left); } };
/** * 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: TreeNode* constructMaximumBinaryTree(vector<int>& nums) { //肯定是需要先知道這個最大值,構造根節點,然後遞迴的構造左右子樹,採用前序遍歷框架 //需要知道子陣列,所以函式的引數不夠,需要自己建構函式,傳入陣列、子陣列頭尾下標 return build(nums, 0, nums.size()-1); } TreeNode* build(vector<int>& nums, int lo, int hi) { if(lo > hi) return nullptr; /*下面這一段是求最大值和最大值索引的,時間複雜度為O(n)*/ int max_num = nums.at(0); int index = 0; for(int i=0;i<=hi;i++) { if(max_num < nums.at(i)) { max_num = nums.at(i); index = i; } } /*****************************/ TreeNode* head = new TreeNode(max_num); head->left = build(nums, 0, index); head->right = build(nums, index+1, hi); return head; } };
/** * 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: TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) { //要先明白,構造一顆二叉樹,首先你得構造出根節點吧,然後你再去構造左右子節點 //根節點簡單,就是前序遍歷的第一個元素,很容易能夠構造 //關鍵是你要構造左右子節點以及往下的,你得知道哪些元素是左子樹哪些是右子樹的,這就需要通過中序序列和已知的根節點的值來確定,這是中序的特性 return build(preorder, 0, preorder.size()-1, inorder, 0, inorder.size()-1); } TreeNode* build(vector<int> preorder, int prestart, int preend, vector<int> inorder, int instart, int inend) { if(prestart > preend) return nullptr; //先儲存根節點的值,找到中序序列中根節點的位置 int rootval = preorder.at(prestart); int index = 0; for(int i=instart;i<=inend;i++) { if(inorder.at(i) == rootval) { index = i; break; } } //以上找到了中序序列中根節點的位置index int leftSize = index - instart;//得到左子樹節點的個數,因為要分開前序遍歷序列 //先構造根節點 TreeNode* root = new TreeNode(rootval); //遞迴構造 root->left = build(preorder, prestart+1, index+leftSize, inorder, instart, index-1); root->right = build(preorder, prestart+leftSize+1, preend, inorder, index+1, inend); return root; } };
/** * 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: TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) { //後序和中序跟前一個一樣的道理,還是應該先構造頭結點,然後遞迴構造左右子樹 //需要新建一個函式進行陣列起止位置控制,別的也沒什麼 //後序最後一個是根節點的值 return build(inorder, 0, inorder.size()-1, postorder, 0, postorder.size()-1); } TreeNode* build(vector<int>& inorder, int inStart, int inEnd, vector<int>& postorder, int postStart, int postEnd) { //base case if(inStart > inEnd) return nullptr; //先儲存根節點的值 int rootVal = postorder.at(postEnd); int index = 0;//記錄中序序列中根節點的位置 for(int i=inStart;i<=inEnd;i++) { if(inorder.at(i) == rootVal) { index = i; break; } } int leftSize = index - inStart; //構造根節點 TreeNode* root = new TreeNode(rootVal); //遞迴構造左右子樹 root->left = build(inorder, inStart, index-1, postorder, postStart, postStart+leftSize-1); root->right = build(inorder, index+1, inEnd, postorder, postStart+leftSize, postEnd-1); return root; } };
/** * 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: bool isSubStructure(TreeNode* A, TreeNode* B) { //第一步:在樹A中找到和樹B的根節點一樣的節點R //第二步:判斷樹A中以R為根節點的子樹是不是包含和樹B一樣的結構 bool result = false; if(A != nullptr && B != nullptr)//只有兩個都不空才去判斷 { if(equal(A->val, B->val)) { //如果找到了跟B節點的根節點一樣的節點,就去判斷第二步 result = doesTree1HaveTree2(A, B); } if(!result) { //如果當前節點跟B根節點不一樣,就去遞迴判斷當前節點的左節點 result = isSubStructure(A->left, B); } if(!result) { //如果當前節點跟B根節點不一樣,就去遞迴判斷當前節點的右節點 result = isSubStructure(A->right, B); } } return result; } bool doesTree1HaveTree2(TreeNode* pRoot1, TreeNode* pRoot2) { if(pRoot2 == nullptr) { return true; } if(pRoot1 == nullptr) { return false; } if(!equal(pRoot1->val, pRoot2->val)) { return false; } return doesTree1HaveTree2(pRoot1->left, pRoot2->left) && doesTree1HaveTree2(pRoot1->right, pRoot2->right);//判斷子樹是不是一樣的 } bool equal(double b1, double b2)//定義double型別是不是相等的判斷 { if(b1-b2<0.0000001 && b1-b2>-0.0000001) return true; else return false; } };
(1)有一道求二叉樹深度的題目,那個題目寫的求深度的演算法可以應用到這道題目裡,思路就是,求得每一個結點的左右子節點的深度,判斷相差不超過1,則是平衡二叉樹
/** * 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: bool isBalanced(TreeNode* root) { if(root == nullptr) return true; //分別得到左右深度 int left = maxDepth(root->left); int right = maxDepth(root->right); if(left-right<-1 && left-right>1) return false; return isBalanced(root->left) && isBalanced(root->right); } int maxDepth(TreeNode* pRoot) { //求以pRoot為根節點的二叉樹深度 if(pRoot == nullptr) return 0; int left = maxDepth(pRoot->left); int right = maxDepth(pRoot->right); return left>right? (left+1) : (right+1); } };
(2)每個節點只遍歷一次的做法
/** * 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: bool isBalanced(TreeNode* root) { //用後序遍歷的方式,遍歷到一個節點之前我們就已經遍歷了它的左右子樹。在遍歷每個節點的時候記錄它的深度 int pDepth; return itIsBalanced(root, &pDepth); } bool itIsBalanced(TreeNode* pRoot, int* pDepth) { if(pRoot == nullptr) { *pDepth = 0; return true; } int left, right; if(itIsBalanced(pRoot->left, &left) && itIsBalanced(pRoot->right, &right))//判斷當前節點的左右子節點為根節點的樹是不是平衡的 { int diff = left - right; if(diff<=1 && diff>=-1)//判斷當前節點為根節點的二叉樹是不是平衡的 { *pDepth = 1 + (left>right ? left : right); return true; //如果子節點為根節點的是平衡的,當前節點為根節點的是平衡的,整個就是平衡的 } } return false; } };
/** * 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: int kthSmallest(TreeNode* root, int k) { //後序遍歷,二叉搜尋樹的後序遍歷是有序的 traverse(root, k); return res; } int res = 0;//記錄結果 int rank = 0;//記錄當前元素的位置排序 void traverse(TreeNod* root, int k) { if(root == nullptr) return; traverse(root->left, k); rank++; if(k == rank) { res = root->val; return; } traverse(root->right, k); } };
/** * 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* convertBST(TreeNode* root) { traverse(root); return root; } //記錄累加和 int sum = 0; void traverse(TreeNode* root) { if(root == nullptr) return; traverse(root->right); //維護累加和 sum += root->val; //將BST轉化成累加樹 root->val = sum; traverse(root->left); } };