Algorithm-Pattern
  • Introduction
  • C & C++
    • C语言
      • C/C++编译器
      • 宏的使用
      • 编译过程
      • 指针 & 数组
      • 柔性数组
      • 函数指针 & 回调函数
      • C标准库之stdio
      • C标准库之string
      • C标准库之errno
      • C标准库之stdarg
      • C标准库之regex
    • C++基础语法
      • 自增(++) & 自减(--)
      • c语言到c++
      • 可变模板参数
      • 强制类型转换
      • C/C++类型转换的本质
      • 指针 & 引用
      • const的用法
      • static的用法
      • 重要的关键字(一)
      • 重要的关键字(二)
      • 内存申请和释放
      • 内联函数
      • 函数 & 运算符重载
      • 面向对象之封装
      • 构造函数 & 析构函数
      • 面向对象之继承
      • 面向对象之多态
      • 泛型编程
      • 异常
      • 再谈函数指针
    • C++并发编程
      • C++的锁
      • 并发与多线程
    • C++高级特性
      • 函数对象
      • 移动语义 & 完美转发
      • lambda表达式
      • RTTI技术
      • RAII技术
      • 智能指针
      • 模板的特化
      • C++静态库和动态库
      • 内存溢出和内存泄漏
    • STL基础
      • String
      • array/vector/list
      • deque/priority_queue
      • set/map
      • unordered_set/unordered_map
      • algorithm_1
      • functional
      • allocator
    • C++标准库
      • IO
      • Tuple
      • regex
      • bitset
      • numeric
    • STL深入源码
      • vector内部实现
      • deque内部实现
      • sort函数实现
    • 第三方库
      • JsonCpp
      • ProtoBuf
  • 数据结构
    • 线性表
    • 字符串
    • 栈和队列
    • 二叉树
    • 平衡二叉树
    • 平衡多路搜索树
    • 树结构的延申
    • 图
    • 二进制
    • 散列表
  • 算法基础
    • 排序算法
    • 查找算法
    • 数学问题
    • 并查集
    • 递归算法
    • 附加——主定理
    • Catalan数
  • 算法设计思想
    • 滑动窗口思想
    • BFS/DFS
    • 二分法
    • 回溯法
    • 贪心算法
    • 分治法
    • 动态规划
    • 分支限界算法
    • 有限状态机(FSM)
  • LeetCode系列
    • 死磕二叉树
    • 股票买卖问题
    • 打家劫舍问题
    • 跳跃游戏问题
    • 括号匹配问题
    • 石子游戏问题
    • 子序列问题
    • 数组 & 矩阵
    • 排列 & 组合
  • 经典算法问题
    • 几何问题
    • 区间问题
    • 背包问题
    • 石子堆问题
    • 表达式求值
  • 面试题
    • 数据结构和算法基础
    • 程序设计题
      • 实现双线程交替打印
      • C++实现读写锁
      • 实现阻塞队列
      • 实现环形队列
      • 实现线程池
      • 实现智能指针
      • 实现string类
      • 实现高性能local cache
      • 实现内存池
      • 生产者-消费者模型
      • 设计定时器
    • 经典的算法题
    • C++面试题总结
    • 面试算法题总结
由 GitBook 提供支持
在本页
  • 1、DFS深度遍历 & 递归
  • Binary Tree Maximum Path Sum
  • Path Sum
  • Path Sum II
  • 2、LCA问题
  • 3、序列化和反序列化
  • 4、二叉树遍历之Morris算法
  • 5、镜像问题
  • 5.1、二叉树的镜像
  • 5.2、判断二叉树是不是对称二叉树
  • 5.3、求X节点的个数(阿里面试题)
  • 5.4、判断两棵二叉树是否同构
  • 6、其他
  • 6.1、逆时针打印完全二叉树的边界节点
  • 6.2、Binary Tree Cameras

这有帮助吗?

  1. LeetCode系列

死磕二叉树

上一页有限状态机(FSM)下一页股票买卖问题

最后更新于4年前

这有帮助吗?

1、DFS深度遍历 & 递归

给定一个非空二叉树,返回其最大路径和。本题中,路径被定义为一条从树中任意节点出发,达到任意节点的序列。该路径至少包含一个节点,且不一定经过根节点。

路径每到一个节点,有 3 种选择: 停下不走。 走到左子节点。 走到右子节点。走到子节点,又面临这 3 种选择。于是有了递归的思路。 不能走进一个分支,又掉头走另一个分支,不符合要求。

定义递归函数

我们关心:如果路径走入一个子树,能从中捞取的最大收益,不关心具体怎么走。 这是一种属于递归的、自顶而下的思考方式。

定义 dfs 函数:返回当前子树能向父节点 “提供” 的最大路径和。

向下:即,一条从父节点延伸下来的路径,能在当前子树中获得的最大收益。它分为三种情况,取其中最大的: 停在当前子树的 root,收益:root.val。 走入左子树,最大收益:root.val + dfs(root.left)。 走入右子树,最大收益:root.val + dfs(root.right)。 当遍历到 null 节点时,收益为 0,所以返回 0。

向上:如果一个子树提供的「最大路径和」为负,如下图。路径走入它,收益不增反减,我们希望这个子树完全不被考虑,所以让它返回 0,成为死支。

int helper(TreeNode *node, int &sum){
    if(!node){
        sum = max(sum, 0);
        return 0;
    }
    int lsum = 0, rsum = 0;
    if(node->left){
        lsum = max(0, helper(node->left, sum));  // 左子树的贡献
    }
    if(node->right){
        rsum = max(0, helper(node->right, sum)); // 右子树的贡献
    }
    sum = max(sum, node->val + lsum + rsum); // 当前子树的 maxSum 挑战最大值
    return node->val + max(lsum, rsum);      // 向父节点提供的最大和,要包括自己
}
int maxPathSum(TreeNode* root){
    int maxSum = INT_MIN;
    helper(root, maxSum);
    return maxSum;
}

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。(分治)

bool hasPathSum(TreeNode* root, int sum){
    if(!root)
        return false;
    if(root && !root->left && !root->right)
        return sum == root->val;
    return hasPathSum(root->left, sum - root->val)
        || hasPathSum(root->right, sum - root->val);
}

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。(回溯)

思路一:迭代,后续遍历,PATH数组和栈同步,到叶节点判断当前路径是否满足,满足就将PATH加到结果中。

思路二:递归,

给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。经典的LCA(Least Common Ancestors)问题。

思路一、后序遍历:因为后序遍历的栈中始终存放着当前要访问节点的祖先节点。所以两次后序遍历,分别遍历到两个节点位置结束,然后两个栈分别保存两个节点的祖先节点,依次弹栈,遇到第一个相同的节点即可。(最应该想到的解法)

思路二、递归:

思路三、倍增法:

思路四、欧拉序+ST表:

思路五、Tarjan 算法:

思路一、后序遍历:因为后序遍历的栈中始终存放着当前要访问节点的祖先节点。所以两次后序遍历,分别遍历到两个节点位置结束,然后两个栈分别保存两个节点的祖先节点,依次弹栈,遇到第一个相同的节点即可。(最应该想到的解法)

思路二、递归:

思路三、倍增法:

思路四、欧拉序+ST表:

思路五、Tarjan 算法:

void getAncestors(TreeNode *root, TreeNode* node, vector<TreeNode *> &astack){
    const TreeNode *cur;
    TreeNode *ptr = root;
    const TreeNode *last = NULL;
    while(ptr != NULL || !astack.empty()){
        while(ptr != NULL){
            astack.push_back(ptr);
            if(ptr == node){
                return;
            }
            ptr = ptr->left;
        }
        cur = astack.back();
        if(cur->right == NULL || cur->right == last){
            astack.pop_back();
            last = cur;
        }else{
            ptr = cur->right;
        }
    }
}
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
    vector<TreeNode *> astack1, astack2;
    getAncestors(root, p, astack1);
    getAncestors(root, q, astack2);
    for(int i = astack1.size() - 1; i >= 0; --i){
        for(int j = astack2.size() - 1; j >= 0; --j){
            if(astack1[i] == astack2[j]){
                return astack1[i];
            }
        }
    }
    return root;
}

拓展:

思路一:BFS 序列成对应的完全二叉树(超时,主要是字符串split耗时长)。

思路二:DFS 前中后序都可以,递归实现。

思路三:改进的BFS,不是完全二叉树,而是扩展二叉树的叶子节点为NULL节点。

// Encodes a tree to a single string.
string serialize(TreeNode* root) {
    string result;
    if(!root)
        return "[]";
    queue<TreeNode *> pqueue;
    pqueue.push(root);
    while(true){
        bool sign = true;
        int len = pqueue.size();
        for(int i = 0; i < len; i++) {
            TreeNode *ptr = pqueue.front();
            pqueue.pop();
            if (ptr) {
                result.append(to_string(ptr->val));
                result.push_back(',');
                if (ptr->left) {
                    pqueue.push(ptr->left);
                    sign = false;
                } else {
                    pqueue.push(NULL);
                }
                if (ptr->right) {
                    pqueue.push(ptr->right);
                    sign = false;
                } else {
                    pqueue.push(NULL);
                }
            } else {
                result.append("X,");
                pqueue.push(NULL);
                pqueue.push(NULL);
            }
        }
        if(sign)
            break;
    }
    while(!result.empty() && (result.back() == ',' || result.back() == 'X'))
        result.pop_back();
    return "[" + result + "]";
}

// Decodes your encoded data to tree.
TreeNode* deserialize(string data) {
    if(data.size() <= 2)
        return NULL;
    vector<string> tokens;
    string str;
    for(int i = 1; i < data.size() - 1; ++i){
        if(data[i] == 'X'){
            tokens.push_back("X");
            i++;
        }else if(data[i] == ','){
            tokens.push_back(str);
            str.clear();
        }else{
            str.push_back(data[i]);
        }
    }
    tokens.push_back(str);
    int len = tokens.size();
    if(tokens.empty())
        return NULL;
    queue<pair<TreeNode *, int> > iqueue;
    TreeNode *root = new TreeNode(stoi(tokens[0]));
    iqueue.push(make_pair(root, 0));
    while(!iqueue.empty()){
        auto ptr = iqueue.front();
        iqueue.pop();
        int idx = 2 * ptr.second;
        if(idx + 1 < len && tokens[idx + 1] != "X"){
            ptr.first->left = new TreeNode(stoi(tokens[idx + 1]));
            if(2 * (idx + 1) + 1 < len)
                iqueue.push(make_pair(ptr.first->left, idx + 1));
        }
        if(idx + 2 < len && tokens[idx + 2] != "X"){
            ptr.first->right = new TreeNode(stoi(tokens[idx + 2]));
            if(2 * (idx + 2) + 1 < len)
                iqueue.push(make_pair(ptr.first->right, idx + 2));
        }
    }
    return root;
}

5.3、求X节点的个数(阿里面试题)

5.4、判断两棵二叉树是否同构

同构是指:两课树可以通过有限次变换左右子树变为同一棵树。如图:

bool Isomorphic(TreeNode* t1, TreeNode* t2){
    if(t1 == NULL && t2 == NULL)
        return true;
    if((t1 == NULL) && (t2 != NULL) || ((t1 != NULL) && (t2 == NULL)))
        return false;
    if(t1->val != t2->val)
        return false;
    if((t1->left == NULL) && (t2->left == NULL))
        return Isomorphic(t1->right, t2->right);
    if(((t1->left != NULL) && (t2->left != NULL))
        &&(t1->left->val == t2->left->val))
        //如果两个都不为空且左儿子相等,应该递归的找左对应左,右对应右
        return Isomorphic(t1->left, t2->left) && Isomorphic(t1->right, t2->right);
    else
        //否则就是交换了,递归的判断左对应右,右对应左
        return Isomorphic(t1->left, t2->right) && Isomorphic(t1->right, t2->left);
}

6.1、逆时针打印完全二叉树的边界节点

完全二叉树用数组存储,从根节点开始逆时针打印完全二叉树的边界节点。

vector<int> BinaryTree::getSeq(int n, vector<int>& tree){
    vector<int> ans;
    if(n == 1){
        ans.push_back(tree[0]);
        return ans;
    }
    // 左边界
    int i = 1;
    while(i - 1 < n){
        ans.push_back(tree[i - 1]);
        i *= 2;
    }
    ans.pop_back();
    // 叶节点
    int j = i / 2 - 2;
    i = i / 2 / 2 - 1;
    for(int k = i; k >= 0 && k < n && k <= j; ++k){
        if(k * 2 + 1 < n)
            ans.push_back(tree[k * 2 + 1]);
        if(k * 2 + 2 < n)
            ans.push_back(tree[k * 2 + 2]);
        if(k * 2 + 1 >= n)
            ans.push_back(tree[k]);
    }
    // 右边界
    while(j > 0){
        ans.push_back(tree[j]);
        j = (j - 2) / 2;
    }
    return ans;
}

2、LCA问题

3、

思路四:类似于,用分隔符包装。

4、二叉树遍历之Morris算法

5、镜像问题

5.1、

5.2、

6、其他

6.2、

✏️
✏️
✏️
✏️
✏️
Binary Tree Maximum Path Sum
Path Sum
Path Sum II
lowest-common-ancestor-of-a-binary-tree
✏️
序列化和反序列化
官方题解方法二
二叉树的镜像
判断二叉树是不是对称二叉树
Binary Tree Cameras