一、二叉樹的遍歷
1.1 二叉樹的層序遍歷
vector<vector<int>> levelOrder(TreeNode* root) {
if(root == nullptr) return {}; //判斷根節點是否為空
queue<TreeNode*> que; //建立佇列
que.push(root); //根節點入隊
vector<int> ret;
while(!que.empty()){
auto tmp = que.front(); //記錄佇列中的第一個節點
que.pop(); //出隊
ret.push_back(tmp->val);//將該節點的值記錄下來
if(tmp->left) que.push(tmp->left); //佇列是先進先出,因此先將左節點入隊
if(tmp->right) que.push(tmp->right); //右節點入隊
}
return ret;
}
1.2 前序,中序,後序遍歷(遞迴)
void dfs(TreeNode* root, vector<int>& res){
if(root==nullptr) return;
//res.push_back(root->val); //前序遍歷
dfs(root->left, res);
//res.push_back(root->val); //中序遍歷
dfs(root->right, res);
//res.push_back(root->val); //後序遍歷
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
dfs(root, res);
return res;
}
1.3 前序,中序,後序遍歷(迭代)
//前序遍歷
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
if(root == nullptr) return {};
stack<TreeNode*>stk;
stk.push(root);
while(!stk.empty()){
TreeNode* tmp = stk.top();
stk.pop();
res.push_back(tmp->val);
if(tmp->right) stk.push(tmp->right);
if(tmp->left) stk.push(tmp->left);
}
return res;
}
//中序遍歷,三個迴圈
vector<int> inorderTraversal(TreeNode* root) {
vector<int>res;
if(root == nullptr) return res;
stack<TreeNode*> stk;
stk.push(root); //根節點入棧
while(!stk.empty()){
//到達了二叉樹的左下角
while(stk.top()->left != nullptr) stk.push(stk.top()->left);
while(!stk.empty()){
TreeNode* tmp = stk.top(); stk.pop(); //遍歷左下角的節點
res.push_back(tmp->val);
if(tmp->right){ //判斷該節點是否存在右子樹,若存在,將右子樹節點入棧,重新找到該右子樹的左下角繼續重複上述過程
stk.push(tmp->right);
break;
}
}
}
return res;
}
//後序遍歷,需要記錄上一次出棧的節點
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
if(root == nullptr) return res;
stack<TreeNode*> stk;
stk.push(root);
TreeNode* lastnode = nullptr;
while(!stk.empty()){
//找到左下角的節點
while(stk.top()->left != nullptr) stk.push(stk.top()->left);
while(!stk.empty()){
//判斷上一次出棧節點是否當前節點的右節點,或者當前節點的右節點是否為空,滿足任一條件,將當前節點輸出,
if(lastnode == stk.top()->right || stk.top()->right == nullptr){
TreeNode* node = stk.top(); stk.pop();
res.push_back(node->val);
lastnode = node;
}
//查詢該節點是否存在右子樹
else if(stk.top()->right != nullptr){
stk.push(stk.top()->right);
break;
}
}
}
return res;
}
二、二叉樹的其他題目
LeetCode 105: 從前序遍歷與中序遍歷序列構造二叉樹
輸入:preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
輸出:[3,9,20,null,null,15,7]
題目分析:
前序遍歷中第一個節點就是根節點,第二個節點就是左子樹的根節點......
中序遍歷中根節點左邊就是左子樹
因此,我們通過前序遍歷來找到節點,通過在中序遍歷中的位置來判斷是左子樹還是右子樹根節點,如下圖所示:
class Solution {
private:
unordered_map<int, int> index;
public:
TreeNode* myBuildTree(const vector<int>& preorder, const vector<int>& inorder, int preorder_left, int preorder_right, int inorder_left, int inorder_right) {
if (preorder_left > preorder_right) {
return nullptr;
}
// 前序遍歷中的第一個節點就是根節點
int preorder_root = preorder_left;
// 在中序遍歷中定位根節點
int inorder_root = index[preorder[preorder_root]];
// 先把根節點建立出來
TreeNode* root = new TreeNode(preorder[preorder_root]);
// 得到左子樹中的節點數目
int size_left_subtree = inorder_root - inorder_left;
// 遞迴地構造左子樹,並連線到根節點
// 先序遍歷中「從 左邊界+1 開始的 size_left_subtree」個元素就對應了中序遍歷中「從 左邊界 開始到 根節點定位-1」的元素
root->left = myBuildTree(preorder, inorder, preorder_left + 1, preorder_left + size_left_subtree, inorder_left, inorder_root - 1);
// 遞迴地構造右子樹,並連線到根節點
// 先序遍歷中「從 左邊界+1+左子樹節點數目 開始到 右邊界」的元素就對應了中序遍歷中「從 根節點定位+1 到 右邊界」的元素
root->right = myBuildTree(preorder, inorder, preorder_left + size_left_subtree + 1, preorder_right, inorder_root + 1, inorder_right);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int n = preorder.size();
// 構造雜湊對映,幫助我們快速定位根節點
for (int i = 0; i < n; ++i) {
index[inorder[i]] = i;
}
return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
}
};
LeetCode 105: 路徑總和
給你二叉樹的根節點 root 和一個表示目標和的整數 targetSum,判斷該樹中是否存在根節點到葉子節點的路徑,這條路徑上所有節點值相加等於目標和 targetSum 。
輸入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
輸出:true
題目分析:
每個節點不僅有它的 val 值,同時還有一些二叉樹的深度,節點數值的和等隱含的屬性。在求路徑和時,當節點遍歷時,將節點與路徑和一起作為遞迴的引數,通過深度優先策略可以判斷是否有等於targetSum的路徑和。
//遞迴法
bool hasPathSum(TreeNode* root, int targetSum) {
if (root == nullptr) {
return false;
}
if(!root->left && !root->right){
if(root->val == targetSum) return true;
else
return false;
}
bool f1 = hasPathSum(root->left, targetSum - root->val); //沿著左子樹去找
bool f2 = hasPathSum(root->right, targetSum - root->val); //沿著右子樹去找
return f1 || f2;
}
//迭代法
bool hasPathSum(TreeNode* root, int targetSum) {
if(root == nullptr) return false;
stack<pair<TreeNode*, int>> stk; //將下一個節點與剩餘的路徑和同時存放到棧內
stk.push(pair<TreeNode*, int>(root, root->val));
while(!stk.empty()){
pair<TreeNode*, int> cur = stk.top(); stk.pop(); //取出棧頂的元素,first 為節點,second 為該節點的 val
TreeNode* node = cur.first;
int path_value = cur.second;
if(node->left == nullptr && node->right == nullptr && path_value==targetSum){
return true;
}
// 深度優先搜尋,其實就是二叉樹的前序遍歷,將路徑上的節點和剩餘路徑和一併存入,如果存在
// node->left == nullptr && node->right == nullptr && path_value==targetSum, 那麼返回 true
if(node->right != nullptr)
stk.push(pair<TreeNode*, int>(node->right, path_value+node->right->val));
if(node->left != nullptr)
stk.push(pair<TreeNode*, int>(node->left, path_value+node->left->val));
}
return false;
}
LeetCode 113: 路徑總和II
給你二叉樹的根節點 root 和一個整數目標和 targetSum ,找出所有 從根節點到葉子節點 路徑總和等於給定目標和的路徑。
輸入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
輸出:[[5,4,11,2],[5,8,4,5]]
題目分析:
這道題跟上題差不多,只不過需要額外定義兩個陣列,res 和 path。其中 path 記錄 dfs 遍歷到的節點,當滿足條件時就把 path 存入 res, 代替 “return true” 這一步。如果不滿足條件,在 dfs 結束時記得彈出 path 的頂端 val, 回退到上一個節點。
//遞迴法
void dfs(TreeNode* root, int targetSum, vector<vector<int>>& res, vector<int>& path){
if (root == nullptr)
return;
path.push_back(root->val);
if(!root->left && !root->right && root->val==targetSum){
res.push_back(path);
}
if(root->left) dfs(root->left, targetSum - root->val, res, path);
if(root->right) dfs(root->right, targetSum - root->val, res, path);
path.pop_back(); //回溯,返回到上一個節點,撤銷處理結果
}
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
vector<vector<int>> res;
vector<int> path;
dfs(root, targetSum, res, path);
return res;
}
LeetCode 199: 二叉樹的右檢視
給定一個二叉樹的 根節點 root,想象自己站在它的右側,按照從頂部到底部的順序,返回從右側所能看到的節點值。
輸入: [1,2,3,null,5,null,4]
輸出: [1,3,4]
題目分析:
這道題採用廣度優先的策略會比較好理解,可以參考 `LeetCode 107: 二叉樹的層序遍歷II`,在層序遍歷中,每一個層的最後一個節點就是所要找到的值。如果採用深度優先演算法來理解,首先從根節點先訪問右子樹來確保右邊的節點首先出現,然後使用一個 depth 變數來判斷該節點是否被擋住。
vector<int> rightSideView(TreeNode* root) {
//廣度優先策略
if(root == nullptr) return {};
queue<TreeNode*> que; //定義層序遍歷的佇列
vector<int> res;
que.push(root);
while(!que.empty()){
int cur_size = que.size(); //記錄該層的節點個數
TreeNode* node = nullptr;
//隨著遍歷,不斷將下一層的節點入隊,當遍歷完該層的節點時,node所代表的就是該層的最右邊節點
for(int i=0; i<cur_size; i++){
node = que.front(); que.pop();
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
res.push_back(node->val);
}
return res;
}
//深度優先策略
void dfs(TreeNode* root, int depth, vector<int>& res){
if(root == nullptr) return;
if(depth == res.size()){ //通過判斷 depth 是否與 res 的長度相等,來判斷該節點是否被擋住
res.push_back(root->val);
}
dfs(root->right, depth+1, res);
dfs(root->left, depth+1, res);
}
vector<int> rightSideView(TreeNode* root) {
//深度優先策略
vector<int> res;
dfs(root, 0, res); //假設根節點的深度為 0
return res;
}
LeetCode 113: 二叉樹的最小深度
給定一個二叉樹,找出其最小深度。最小深度是從根節點到最近葉子節點的最短路徑上的節點數量。
輸入:root = [3,9,20,null,null,15,7]
輸出:2
題目分析:
本題同樣可以採取 DFS 和 BFS 的策略。
使用 BFS 進行層序遍歷,當碰到第一個節點 node 且 node->left 與 node->right 均為 nullptr 時,該節點的深度就是二叉樹的最小深度。
使用 DFS 進行前序遍歷,必須要記錄每一條路徑的深度,然後最小的那個就是結果。
//廣度優先策略
int minDepth(TreeNode* root) {
int res=0;
if(root == nullptr) return res;
queue<TreeNode*> que;
que.push(root);
while(!que.empty()){
int cur_size = que.size(); //記錄下該層的節點個數
res++; //沒經過一層,深度加 1
for(int i=0; i<cur_size; i++){
TreeNode* node = que.front(); que.pop();
//如果該層中有某個節點的左右子節點都為空,那麼提前返回結果
if(node->left == nullptr && node->right == nullptr)
return res;
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
}
return res;
}
//深度優先策略
int minDepth(TreeNode *root) {
if (root == nullptr) {
return 0;
}
if (root->left == nullptr && root->right == nullptr) {
return 1;
}
int min_depth = INT_MAX;
if (root->left != nullptr) {
min_depth = min(minDepth(root->left), min_depth);
}
if (root->right != nullptr) {
min_depth = min(minDepth(root->right), min_depth);
}
return min_depth + 1;
}