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(logN)。
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;}的结构