LeetCode系列之二叉树专题

1. 二叉树题目概述

https://leetcode-cn.com/tag/tree/

页面描述的是“树”的概念,本专题以二叉树题目为主。

2. 典型题目

2.1 二叉树的层序遍历

https://leetcode-cn.com/problems/binary-tree-level-order-traversal/

解题技巧:出现树的层次遍历,考虑用队列作为辅助结构。

vector<vector<int>> levelOrder(TreeNode* root) {
    vector<vector<int>> result;
    if (root == nullptr) return result;

    queue<TreeNode*> treeNodeQueue;
    treeNodeQueue.push(root);

    while (!treeNodeQueue.empty()) {
        int currentLevelSize = treeNodeQueue.size();
        result.push_back(vector<int>());
        for (int i = 0; i < currentLevelSize; i++) {
            TreeNode* node = treeNodeQueue.front();
            treeNodeQueue.pop();
            result.back().push_back(node->val);
            if (node->left) treeNodeQueue.push(node->left);
            if (node->right) treeNodeQueue.push(node->right);
        }
    }

    return result;
}

可以简单的理解为,凡是“遍历”的操作,时间复杂度都是O(N)。

时间复杂度O(N);空间复杂度O(N)。

2.2 二叉树中的最大路径和

https://leetcode-cn.com/problems/binary-tree-maximum-path-sum/

int maxPathSum(TreeNode* root) {
    int maxSum = INT_MIN;
    maxGain(root, maxSum);
    return maxSum;
}

int maxGain(TreeNode* node, int& maxSum) {
    if (node == nullptr) return 0;

    int leftGain = max(maxGain(node->left, maxSum), 0);
    int rightGain = max(maxGain(node->right, maxSum), 0);

    // 顺便更新最大值
    int newPathSum = node->val + leftGain + rightGain;
    maxSum = max(newPathSum, maxSum);

    return node->val + max(leftGain, rightGain);
}

maxSum用了局部变量,是担心成员变量会在各测试case之间有影响。不过看题解,可能是我多虑了。

另外,下面这个point也值得学习。

// 简洁写法
maxSum = max(newPathSum, maxSum);

// 以前常用的笨拙写法
if (newPathSum > maxSum) {
    maxSum = newPathSum;
}

时间复杂度O(N);空间复杂度O(N)。

2.3 路径总和

https://leetcode-cn.com/problems/path-sum/

bool hasPathSum(TreeNode* root, int sum) {
    if (root == nullptr) return false;

    if (root->left == nullptr && root->right == nullptr) {
        return sum == root->val;
    }

    return hasPathSum(root->left, sum - root->val) ||
           hasPathSum(root->right, sum - root->val);
}

时间复杂度O(N);空间复杂度O(H),空间复杂度主要取决于递归时栈空间的开销,最坏情况下,树呈现链状,空间复杂度为 O(N),平均情况下树的高度与节点数的对数正相关,空间复杂度为 O(log⁡N)。

https://leetcode-cn.com/problems/path-sum-ii/

vector<vector<int>> pathSum(TreeNode* root, int sum) {
    vector<vector<int>> result;
    vector<int> ivec;
    pathSumTrace(root, sum, ivec, result);
    return result;
}

void pathSumTrace(TreeNode* node, 
                  int sum, 
                  vector<int>& ivec, 
                  vector<vector<int>>& result) {
    if (node == nullptr) return;
    ivec.push_back(node->val);

    if (node->left == nullptr && node->right == nullptr) {
        if (sum == node->val) { 
            result.push_back(ivec);
        }
    }

    pathSumTrace(node->left, sum - node->val, ivec, result);
    pathSumTrace(node->right, sum - node->val, ivec, result);

    ivec.pop_back();
}

这里跟答案学到的一个技巧是,ivec.pop_back这一句,有点回溯的意味在里边。之前的自己想到的解法是:利用函数传参复制 ivec,符合条件的追加到最终结果,不符合就直接忽略。看起来很简洁,但是毕竟效率不高。

时间复杂度O(N);空间复杂度O(H)。

https://leetcode-cn.com/problems/path-sum-iii/

int pathSum(TreeNode* root, int sum) {
    if (root == nullptr) return 0;

    int left = pathSum(root->left, sum);
    int right = pathSum(root->right, sum);
    int middle = pathSumThroughRoot(root, sum);

    return left + right + middle;
}

int pathSumThroughRoot(TreeNode* root, int sum) {
    if (root == nullptr) return 0;

    int ret = 0;
    // 注意这里ret++之后,并没有直接返回。节点值有正有负,所以还没有最终结束。
    if (root->val == sum) ret++;

    ret += pathSumThroughRoot(root->left, sum - root->val);
    ret += pathSumThroughRoot(root->right, sum - root->val);

    return ret;
}

时间复杂度O(N2),空间复杂度O(H2)。

个人理解,因为是双重递归,所以复杂度乘方了。

2.4 对称二叉树

https://leetcode-cn.com/problems/symmetric-tree/

bool isSymmetric(TreeNode* root) {
    // 这里两个形参都用root,避免了显式check参数是否为空的啰嗦写法。
    // 从逻辑上也可以理解为:一个数和自己是镜像关系,那么它是对称的。
    return isMirrorTree(root, root);
}

bool isMirrorTree(TreeNode* t1, TreeNode* t2) {
    // 这里的参数检查很简洁
    if (t1 == nullptr && t2 == nullptr) return true;
    if (t1 == nullptr || t2 == nullptr) return false;
    return (t1->val == t2->val)
           && isMirrorTree(t1->left, t2->right)
           && isMirrorTree(t1->right, t2->left);
}

时间复杂度O(N);空间复杂度O(N)。

2.5 二叉树的最大深度

https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/

2.6 左叶子之和

https://leetcode-cn.com/problems/sum-of-left-leaves/

2.7 二叉搜索树的最近公共祖先

https://leetcode-cn.com/problems/lowest-common-ancestor-of-a-binary-search-tree/

2.8 验证二叉搜索树

https://leetcode-cn.com/problems/validate-binary-search-tree/

2.9 把二叉搜索树转换为累加树

https://leetcode-cn.com/problems/convert-bst-to-greater-tree/

2.10 二叉树展开为链表

https://leetcode-cn.com/problems/flatten-binary-tree-to-linked-list/

 题目要求原地算法:在计算机科学中,一个原地算法(in-place algorithm)是一种使用小的,固定数量的额外之空间来转换资料的算法。当算法执行时,输入的资料通常会被要输出的部份覆盖掉。

直观解法:

void flatten(TreeNode* root) {
    while (root != nullptr) {
        if (root->left == nullptr) {
            root = root->right;
            continue;
        }
        TreeNode* rightMost = findRightMostTreeNode(root->left);
        // 原右子树挂到左子树的最右节点上
        rightMost->right = root->right;
        // 左子树挂到右边
        root->right = root->left;
        // 左子树置空
        root->left = nullptr;
        // 步进
        root = root->right;
    }
}

TreeNode* findRightMostTreeNode(TreeNode* t) {
    if (t == nullptr) return nullptr;
    while (t->right != nullptr) {
        t = t->right;
    }
    return t;
}

递归解法:

void flatten(TreeNode* root) {
    if (root == nullptr) return;

    flatten(root->left);
    flatten(root->right);

    TreeNode* rightSubTree = root->right;
    root->right = root->left;
    root->left = nullptr;
    
    TreeNode* rightMost = findRightMostTreeNode(root);
    rightMost->right = rightSubTree;
}

时间复杂度O(N),空间复杂度O(1)。

3. 总结

做算法,背单词

symmetric 对称的,均衡的

解题技巧

  • 树的层次遍历,考虑用队列做辅助结构。
  • max函数替代if (a > b) {b = a;}的结构
posted @ 2020-07-06 09:24  不写诗的诗人小安  阅读(171)  评论(0编辑  收藏  举报