二叉树的路径问题个人总结

83 阅读5分钟

二叉树的所有路径

因为需要遍历整棵二叉树,获取所有的节点,并且回溯的过程不需要处理信息。故采用没有返回值的回溯算法。回溯与递归差不多。但它主要的区别是它在遍历的过程中,有着保存元素的动作,并且伴随着函数帧的调用,有着对元素push/pop的动作。 这主要有两种形式,一种是在函数调用时构造临时值(值传递),无需有显示的pop操作;另一种是引用传递或者采用全局变量保存中间值,这样在每个函数返回的时候都需要有显示的pop操作。本题便是采用构造临时变量进行值传递的做法。

 * 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<string> result;
    void backtrack(TreeNode* root, string path) {
        //遇到空节点则返回
        if(root == nullptr) return;
        //遇到叶子节点则添加末尾,并添加到结果中
        if(root->left == nullptr && root->right == nullptr) {
            path += to_string(root->val);
            result.push_back(path);
        }
        //值传递+临时构造参数,相当于回溯,故没有显示的pop操作
        backtrack(root->left, path + to_string(root->val) + "->");
        backtrack(root->right, path + to_string(root->val) + "->");
    }
    vector<string> binaryTreePaths(TreeNode* root) {
        backtrack(root, "");
        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:
    bool hasPathSum(TreeNode* root, int targetSum) {
        /*遇到为空的非叶子节点返回false*/
        if(root == nullptr) return false;
        /*叶子节点,且值为0返回true*/
        if(!root->left && !root->right) {
            return !(targetSum - root->val);
        }
        return hasPathSum(root->left, targetSum - root->val) ||
        hasPathSum(root->right, targetSum - root->val);
    }
};

或者采用以下做法,带有返回值的递归,可以避免递归所有子树。

 * 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:
    bool search(TreeNode* root, int targetSum) {
        if(!root->left && !root->right) {
            return !(targetSum - root->val);
        }
        if(root->left) {
            if(search(root->left, targetSum - root->val)) return true;
        }
        if(root->right) {
            if(search(root->right, targetSum - root->val)) return true;
        }
        return false;
    }
    bool hasPathSum(TreeNode* root, int targetSum) {
        if(root == nullptr) return false;
        return search(root, targetSum);
    }
};

二叉树的路径总和Ⅱ

本题要求找到所有符合要求的路径。采用vector保存路径,只能采用引用或者全局变量的做法。在递归调用子函数返回后,必须对元素进行弹出。这里还有一个细节,就是调用函数前要不要对子节点进行判断非空。如果不判断的话,形式如下:

if(root == nullptr) return;
if(!root->left && !root->right && targetSum - root->val == 0) {
    result.push_back(path);
}
backtrack(root->left);
path.pop_back();
backtrack(root->right);
path.pop_back();

若调用前不判断非空,则函数的第一句必须判断节点是否为空,若为空则返回。但在采用全局变量或者采用引用,有着显式的push/pop操作的递归时,必须在调用前判断是否非空。否则会因为遇到空节点返回,没有push元素,返回后缺多弹出了一个元素,引发错误。

 * 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<vector<int>> result;
    vector<int> path;
    void backtrack(TreeNode* root, int targetSum) {
        /*将当前层的节点放入path中保存*/
        path.push_back(root->val);
        if(!root->left && !root->right) {
           if(targetSum - root->val == 0) {
               result.push_back(path);
           }
           return;
        } 
        /*对于采用非值传递保存中间结果的回溯题,必须先判非空再递归
        因为若采用“先污染再治理”的办法,遇到空节点则返回
        在空节点这层没有Push节点,返回的时候却错误地pop了节点*/
        if(root->left) {
            backtrack(root->left, targetSum - root->val);
            path.pop_back();
        }
        if(root->right) {
            backtrack(root->right, targetSum - root->val);
            path.pop_back();
        }
    }
    vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
        backtrack(root, targetSum);
        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:
    int cnt = 0;
    /*采用值传递,无显示的push/pop操作,故递归的时候不需要判非空*/
    void backtrack(TreeNode* root, long long targetSum) {
        if(root == nullptr) return;
        if(targetSum - root->val == 0) {
            cnt++;
        }
        backtrack(root->left, targetSum - root->val);
        backtrack(root->right, targetSum - root->val);
    }
    void traversal(TreeNode* root, int targetSum) {
        if(root == nullptr) return;
        backtrack(root, targetSum);
        traversal(root->left, targetSum);
        traversal(root->right, targetSum);
    }
    int pathSum(TreeNode* root, int targetSum) {
        if(root == nullptr) return 0;
        traversal(root, targetSum);
        return cnt;
    }
};

二叉树最大路径和

对于每个节点的贡献度,只需要计算当前root->val和子节点贡献度的较大值的和。而对于路径和而言,则情况特别多,通过在返回的时候和0比较取较大值,避免了各种情况的讨论。因为取0的时候相当于没有加上。

 * 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 maxnum = INT32_MIN;
    int maxgain(TreeNode* root) {
        if(root == nullptr) return 0;

        int leftGain = max(maxgain(root->left), 0);
        int rightGain = max(maxgain(root->right), 0);

        int num = root->val + leftGain + rightGain;
        maxnum = max(maxnum, num);

        return root->val + max(leftGain, rightGain);
    }
    int maxPathSum(TreeNode* root) {
        if(root == nullptr) return 0;
        maxgain(root);
        return maxnum;
    }
};

二叉树中的伪回文路径

在遍历的时候不保存路径节点序列,而是直接保存节点值得出现次数。就避免了超时。

 * 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 count = 0;
    unordered_map<int, int> map;
    void backtrack(TreeNode* root) {
        /*必须是叶子节点,而不是遇到空节点*/
        map[root->val]++;
        if(!root->left && !root->right) {
            int cnt = 0;
            for(auto ite : map) {
                if(ite.second % 2 == 1) cnt++;
            }
            if(cnt <= 1) count++;
        }
        if(root->left) {
            backtrack(root->left);
            map[root->left->val]--;
        }
        if(root->right) {
            backtrack(root->right);
            map[root->right->val]--;
        }
    }
    int pseudoPalindromicPaths (TreeNode* root) {
        backtrack(root);
        return count;
    }
};