# 找树左下角的值
题目链接🔗
# 迭代
层序遍历找最后一行第一个值。
class Solution { | |
public: | |
int findBottomLeftValue(TreeNode* root) { | |
queue<TreeNode*> que; | |
if (root != nullptr) que.push(root); | |
int result = 0; | |
while (!que.empty()) { | |
int size = que.size(); | |
for (int i = 0; i < size; i++) { | |
TreeNode* node = que.front(); | |
que.pop(); | |
if (i == 0) result = node->val; // 记录最后一行第一个元素 | |
if (node->left) que.push(node->left); | |
if (node->right) que.push(node->right); | |
} | |
} | |
return result; | |
} | |
}; |
# 递归
使用递归判断是否是最后一行,其实就是看找深度最大的子叶节点。
如何判断最左边的节点?递归时保证优先左边搜索,然后记录深度最大的子叶节点,此时就是树的最后一行最左边的值。
- 确定递归函数的参数和返回值
参数必须有要遍历的树的根节点,还有就是一个 int 型的变量用来记录最长深度。
还需要类里的两个全局变量,maxLen 用来记录最大深度,result 记录最大深度最左节点的数值。
- 确定终止条件
当遇到叶子节点的时候,就需要统计一下最大深度。所以终止条件就是遇到叶子节点。
- 确定单层递归的逻辑
找最大深度的时候依然使用回溯。
class Solution { | |
public: | |
int maxDepth = INT_MIN; | |
int result; | |
void traversal(TreeNode* root, int depth) { | |
if (root->left == nullptr && root->right == nullptr) { | |
if (depth > maxDepth) { | |
maxDepth = depth; | |
result = root->val; | |
} | |
return; | |
} | |
if (root->left) { | |
traversal(root->left, depth + 1); // 隐藏着回溯 | |
} | |
if (root->right) { | |
traversal(root->right, depth + 1); // 隐藏着回溯 | |
} | |
return; | |
} | |
int findBottomLeftValue(TreeNode* root) { | |
traversal(root, 0); | |
return result; | |
} | |
}; |
# 路径总和
题目链接🔗
要遍历从根节点到叶子节点的路径看总和是否等于目标和,可以用 DFS 遍历。
- 确定递归函数的参数和返回值
参数需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和,用以是否正好是目标和。
遍历并不需要遍历整棵树,所以需要返回值。
- 确定终止条件
首先计数器如何统计这一条路径的和呢?
不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器 count 初始为目标和,然后每次减去遍历路径节点上的数值。
如果最后,同时到了叶子节点的话,说明找到了目标和。
如果遍历到了叶子节点,count 不为 0,就是没找到。
- 确定单层递归逻辑
因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。
递归函数是有返回值的,如果递归函数返回 true,说明找到了合适的路径,应该立刻返回。
class Solution { | |
private: | |
bool traversal(TreeNode* cur, int count) { | |
if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为 0 | |
if (!cur->left && !cur->right) return false; // 遇到叶子节点直接返回 | |
if (cur->left) { // 左 | |
count -= cur->left->val; // 递归,处理节点; | |
if (traversal(cur->left, count)) return true; | |
count += cur->left->val; // 回溯,撤销处理结果 | |
} | |
if (cur->right) { // 右 | |
count -= cur->right->val; // 递归,处理节点; | |
if (traversal(cur->right, count)) return true; | |
count += cur->right->val; // 回溯,撤销处理结果 | |
} | |
return false; | |
} | |
public: | |
bool hasPathSum(TreeNode* root, int targetSum) { | |
if (root == nullptr) return false; | |
return traversal(root, targetSum - root->val); | |
} | |
}; |
# 从中序遍历和后序遍历构造二叉树
题目链接🔗
以后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
流程如图:
递归分为以下几步:
- 如果数组为空,是空节点。
- 如果数组不为空,取数组最后一个元素为节点元素。
- 找到后续数组第一个元素在中序数组的位置,作为切割点。
- 切割中序数组,切成中序左数组和右数组。
- 切割后序数组,切成后序左数组和右数组。
- 递归处理左区间和右区间。
TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) { | |
// 第一步 | |
if (postorder.size() == 0) return nullptr; | |
// 第二步:后序遍历数组最后一个元素,就是当前的中间节点 | |
int rootValue = postorder[postorder.size() - 1]; | |
TreeNode* root = new TreeNode(rootValue); | |
// 叶子节点 | |
if (postorder.size() == 1) return root; | |
// 第三步:找切割点 | |
int delimiterIndex; | |
for (delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++) { | |
if (inorder[delimiterIndex] == rootValue) break; | |
} | |
// 第四步:切割中序数组,得到 中序左数组和中序右数组 | |
// 第五步:切割后序数组,得到 后序左数组和后序右数组 | |
// 第六步 | |
root->left = traversal(中序左数组, 后序左数组); | |
root->right = traversal(中序右数组, 后序右数组); | |
return root; | |
} |
切割一定要确定区间的标准。是左闭右开,还有左开右闭,还是左闭右闭,这个就是不变量,要在递归中保持这个不变量。
中序数组很好切割,而后序数组没有明确的切割点。
但是,中序数组大小一定是和后序数组大小相同,因此可以用中序数组的 size 来切割后序数组。
class Solution { | |
private: | |
// 中序区间:[inorderBegin, inorderEnd),后序区间 [postorderBegin, postorderEnd) | |
TreeNode* traversal (vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& postorder, int postorderBegin, int postorderEnd) { | |
if (postorderBegin == postorderEnd) return nullptr; | |
int rootValue = postorder[postorderEnd - 1]; | |
TreeNode* root = new TreeNode(rootValue); | |
if (postorderEnd - postorderBegin == 1) return root; | |
int delimiterIndex; | |
for (delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) { | |
if (inorder[delimiterIndex] == rootValue) break; | |
} | |
// 切割中序数组 | |
// 左中序区间,左闭右开 [leftInorderBegin, leftInorderEnd) | |
int leftInorderBegin = inorderBegin; | |
int leftInorderEnd = delimiterIndex; | |
// 右中序区间,左闭右开 [rightInorderBegin, rightInorderEnd) | |
int rightInorderBegin = delimiterIndex + 1; | |
int rightInorderEnd = inorderEnd; | |
// 切割后序数组 | |
// 左后序区间,左闭右开 [leftPostorderBegin, leftPostorderEnd) | |
int leftPostorderBegin = postorderBegin; | |
int leftPostorderEnd = postorderBegin + delimiterIndex - inorderBegin; // 终止位置是 需要加上 中序区间的大小 size | |
// 右后序区间,左闭右开 [rightPostorderBegin, rightPostorderEnd) | |
int rightPostorderBegin = postorderBegin + (delimiterIndex - inorderBegin); | |
int rightPostorderEnd = postorderEnd - 1; // 排除最后一个元素,已经作为节点了 | |
root->left = traversal(inorder, leftInorderBegin, leftInorderEnd, postorder, leftPostorderBegin, leftPostorderEnd); | |
root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, postorder, rightPostorderBegin, rightPostorderEnd); | |
return root; | |
} | |
public: | |
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) { | |
if (inorder.size() == 0 || postorder.size() == 0) return nullptr; | |
// 左闭右开的原则 | |
return traversal(inorder, 0, inorder.size(), postorder, 0, postorder.size()); | |
} | |
}; |
# 最大二叉树
题目链接🔗
构造树一般采用的是前序遍历,因为先构造中间节点,然后递归构造左子树和右子树。
- 确定递归函数的参数和返回值
参数传入的是存放元素的数组,返回该数组构造的二叉树的头结点。
- 确定终止条件
题目中说了输入的数组大小一定是大于等于 1 的,所以不用考虑小于 1 的情况,当递归遍历的时候,如果传入的数组大小为 1,说明遍历到了叶子节点了。
那么应该定义一个新的节点,并把这个数组的数值赋给新的节点,然后返回这个节点。 这表示一个数组大小是 1 的时候,构造了一个新的节点,并返回。
- 确定单层递归的逻辑
- 先要找到数组中最大的值和对应的下标, 最大的值构造根节点,下标用来下一步分割数组。
- 最大值所在的下标左区间构造左子树。
- 最大值所在的下标右区间构造右子树。
class Solution { | |
private: | |
// 在左闭右开区间 [left, right),构造二叉树 | |
TreeNode* traversal(vector<int>& nums, int left, int right) { | |
if (left >= right) return nullptr; | |
// 分割点下标:maxValueIndex | |
int maxValueIndex = left; | |
for (int i = left + 1; i < right; ++i) { | |
if (nums[i] > nums[maxValueIndex]) maxValueIndex = i; | |
} | |
// 中间节点 | |
TreeNode* root = new TreeNode(nums[maxValueIndex]); | |
// 构造左子树,左闭右开:[left, maxValueIndex) | |
root->left = traversal(nums, left, maxValueIndex); | |
// 构造右子树,左闭右开:[maxValueIndex + 1, right) | |
root->right = traversal(nums, maxValueIndex + 1, right); | |
return root; | |
} | |
public: | |
TreeNode* constructMaximumBinaryTree(vector<int>& nums) { | |
return traversal(nums, 0, nums.size()); | |
} | |
}; |