部分二叉樹題目彙總

ZhiboZhao 發表於 2021-08-30
演算法

一、二叉樹的遍歷

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;
}