今天我想分享三个经典的二叉树算法题目,它们分别考察了二叉树的不同操作方式:层序遍历、路径搜索和结构重构。这些题目在面试和日常编码中都非常常见,掌握它们能帮助我们建立扎实的树形数据结构基础。
题目要求找出二叉树最底层最左边的节点值。这个问题看似简单,但需要考虑几个关键点:
层序遍历(BFS)是解决这类层级相关问题的自然选择,因为它能按层级顺序遍历节点,方便我们定位到最后一层。
cpp复制class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
int res;
deque<TreeNode*> dq;
dq.push_back(root);
while (!dq.empty()) {
int sz = dq.size();
for (int i = 0; i < sz; i++) {
TreeNode* tmp = dq.front();
dq.pop_front();
if (!tmp->left && !tmp->right)
if (i == 0)
res = tmp->val;
if (tmp->left)
dq.push_back(tmp->left);
if (tmp->right)
dq.push_back(tmp->right);
}
}
return res;
}
};
这段代码有几个值得注意的细节:
提示:在实际面试中,可以指出这种解法的时间复杂度是O(n),空间复杂度在最坏情况下也是O(n)(当树退化为链表时)。
虽然BFS更直观,但DFS也可以解决这个问题。我们可以记录遍历时的深度,当遇到更深的第一个节点时更新结果:
cpp复制class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
int maxDepth = -1, res = 0;
function<void(TreeNode*, int)> dfs = [&](TreeNode* node, int depth) {
if (!node) return;
if (depth > maxDepth) {
maxDepth = depth;
res = node->val;
}
dfs(node->left, depth + 1);
dfs(node->right, depth + 1);
};
dfs(root, 0);
return res;
}
};
这种DFS解法同样高效,且代码更简洁。它利用了DFS会优先访问左子树的特性,确保在相同深度下总是先访问左侧节点。
题目要求判断二叉树中是否存在从根到叶子的路径,其节点值之和等于给定目标值。这需要:
DFS是这类路径遍历问题的标准解法,因为它天然适合探索单条路径到底。
cpp复制class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
bool res = false;
auto dfs = [&](this auto&& dfs, TreeNode* node, int last) {
if (!node)
return;
if (res)
return;
last += node->val;
if (!node->left && !node->right) {
if (last == targetSum)
res = true;
}
if (node->left)
dfs(node->left, last);
if (node->right)
dfs(node->right, last);
};
dfs(root, 0);
return res;
}
};
关键点说明:
last参数累积当前路径和如果需要返回所有符合条件的路径而不仅仅是判断存在性,我们需要记录路径节点:
cpp复制class Solution {
public:
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
vector<vector<int>> res;
vector<int> tmp;
auto dfs = [&](this auto&& dfs, TreeNode* node, int pre) {
if (!node)
return;
pre += node->val;
tmp.push_back(node->val);
if (!node->left && !node->right) {
if (pre == targetSum)
res.push_back(tmp);
}
if (node->left)
dfs(node->left, pre);
if (node->right)
dfs(node->right, pre);
tmp.pop_back();
};
dfs(root, 0);
return res;
}
};
这个版本引入了回溯思想:
tmp向量记录当前路径注意:这里必须复制tmp而不是直接引用,因为tmp会在回溯过程中被修改。
这是二叉树重构的经典问题,给定中序和后序遍历序列,要求重建原始二叉树。主要难点在于:
cpp复制class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
unordered_map<int, int> inMap;
for (int i = 0; i < inorder.size(); i++)
inMap[inorder[i]] = i;
function<TreeNode*(int, int, int, int)> build = [&](int inStart, int inEnd, int postStart, int postEnd) {
if (inStart > inEnd) return (TreeNode*)nullptr;
TreeNode* root = new TreeNode(postorder[postEnd]);
int inRoot = inMap[root->val];
int numsLeft = inRoot - inStart;
root->left = build(inStart, inRoot - 1, postStart, postStart + numsLeft - 1);
root->right = build(inRoot + 1, inEnd, postStart + numsLeft, postEnd - 1);
return root;
};
return build(0, inorder.size() - 1, 0, postorder.size() - 1);
}
};
类似后序+中序的思路,但前序的第一个元素是根节点:
cpp复制TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
unordered_map<int, int> inMap;
for (int i = 0; i < inorder.size(); i++)
inMap[inorder[i]] = i;
function<TreeNode*(int, int, int, int)> build = [&](int preStart, int preEnd, int inStart, int inEnd) {
if (preStart > preEnd) return (TreeNode*)nullptr;
TreeNode* root = new TreeNode(preorder[preStart]);
int inRoot = inMap[root->val];
int numsLeft = inRoot - inStart;
root->left = build(preStart + 1, preStart + numsLeft, inStart, inRoot - 1);
root->right = build(preStart + numsLeft + 1, preEnd, inRoot + 1, inEnd);
return root;
};
return build(0, preorder.size() - 1, 0, inorder.size() - 1);
}
上述问题都可以用迭代法实现。例如路径总和的迭代DFS:
cpp复制bool hasPathSum(TreeNode* root, int targetSum) {
if (!root) return false;
stack<pair<TreeNode*, int>> st;
st.push({root, root->val});
while (!st.empty()) {
auto [node, sum] = st.top();
st.pop();
if (!node->left && !node->right && sum == targetSum)
return true;
if (node->right)
st.push({node->right, sum + node->right->val});
if (node->left)
st.push({node->left, sum + node->left->val});
}
return false;
}
这种迭代解法使用显式栈代替递归,避免了递归深度限制问题。