513.找树左下角的值
题目
给定一个二叉树的 根节点 root
,请找出该二叉树的 最底层 最左边 节点的值。
假设二叉树中至少有一个节点。
示例:
输入: root = [2,1,3]
输出: 1
提示:
-
二叉树的节点个数的范围是
[1,10(4)]
-
-2(31) <= Node.val <= 2(31) - 1
思路
首先是要找到最后一行,然后才是最左边的值
使用递归法,深度最大的叶子节点一定是最后一行
最左边可以用前序遍历,保证优先左边搜索,记录深度最大的叶子节点,此时就是树的最后一行最左边的值
递归三部曲
-
确定递归函数的参数和返回值
- 参数为节点和深度,不需要返回值,返回类型为 void,定义两个全局变量,记录最大深度和最大深度最左节点的数值
-
确定终止条件
- 遇到叶子节点的时候,就需要统计最大的深度
-
确定单层递归的逻辑
- 在找最大深度的时候,递归的过程中依然要使用回溯
代码实现
-
定义全局变量
- int maxDepth = INT_NULL;
- int result;
-
定义函数返回值和参数:void traversal(root, depth)
-
终止条件:
- 遍历到叶子节点:if(root->left == NULL && root->right == NULL)
- if(depth > macDepth)
- maxDepth = depth;
- result = root->val;
- if(depth > macDepth)
- 遍历到叶子节点:if(root->left == NULL && root->right == NULL)
-
单层递归逻辑:
- 左:if(root->left)
- depth++
- traversal(root->left, depth)
- 回溯:depth–
- 右:if(root->right)
- depth++
- traversal(root->right, depth)
- 回溯:depth–
- (没有中的处理逻辑)
- 左:if(root->left)
class Solution {
public:
int maxDepth = INT_MIN;
int result;
void traversal(TreeNode* root, int depth){
if(root->left == NULL && root->right == NULL){
if(depth > maxDepth){
maxDepth = depth;
result = root->val;
}
return;
}
if(root->left){
depth++;
traversal(root->left, depth);
depth--;
}
if(root->right){
depth++;
traversal(root->right, depth);
depth--;
}
return;
}
int findBottomLeftValue(TreeNode* root) {
traversal(root, 0);
return result;
}
};
精简后:
class Solution {
public:
int maxDepth = INT_MIN;
int result;
void traversal(TreeNode* node, int depth){
if(node->left == NULL && node->right == NULL){
if(depth > maxDepth){
maxDepth = depth;
result = node->val;
}
return;
}
if(node->left)traversal(node->left, depth + 1);
if(node->right)traversal(node->right, depth + 1);
}
int findBottomLeftValue(TreeNode* root) {
traversal(root, 0);
return result;
}
};
回溯:
traversal(root->left, depth + 1); // 隐藏着回溯
traversal(root->right, depth + 1); // 隐藏着回溯
路径总和
112.路径总和
题目
给你二叉树的根节点 root
和一个表示目标和的整数 targetSum
。判断该树中是否存在 根节点到叶子节点 的路径,这条路径上所有节点值相加等于目标和 targetSum
。如果存在,返回 true
;否则,返回 false
。
叶子节点 是指没有子节点的节点。
示例:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,null,1], targetSum = 22
输出:true
解释:等于目标和的根节点到叶节点路径如上图所示。
提示:
-
树中节点的数目在范围
[0, 5000]
内 -
-1000 <= Node.val <= 1000
-
-1000 <= targetSum <= 1000
思路
-
确定递归函数的参数和返回值
- 参数:需要二叉树的根节点,需要一个计数器,来计算二叉树的一条边之和是否正好是目标和,计数器 int 型
- 返回值:
- 递归函数什么时候需要返回值,什么时候不需要,总结如下:
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值(113.路径总和Ⅱ)
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值(236.二叉树的最近公共祖先)
- 如果搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回(本体情况)
- 递归函数什么时候需要返回值,什么时候不需要,总结如下:
- 本题需要返回值类型:如图,遍历的路线,并不需要遍历整棵树,所以递归函数返回值可以用 bool 类型表示
-
确定终止条件
计数器 count 初始值为目标和,如何每次减去遍历路径节点上的数值,如果最后 count == 0,同时到了叶子节点,说明找到了目标和,如果遍历了叶子节点,count 不为 0,就是没找到 -
确定单层递归的逻辑
因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了递归函数是有返回值的,如果递归函数返回 true,说明找到了合适的路径,要立刻返回
代码实现
-
定义 bool 类型,传入节点和计数器:bool traversal(TreeNode* node, int count)
-
终止条件:
- if(node->left == NULL && node->right == NULL && count == 0) return true
- if(node->left == NULL && node->right == NULL && count != 0) return false
-
单层递归逻辑
- 向左遍历:if(node->left){
- count -= node->left->val
- if(traserval(node->left, count)) return true
- 回溯:count += node->left->val
- 向右遍历:if(node->right){
- count -= node->right->val
- if(traserval(node->right, count)) return true
- 回溯:count += node->left->val
- return false
- 向左遍历:if(node->left){
class Solution {
private:
bool traversal(TreeNode* cur, int count){
if(!cur->left && !cur->right && count == 0)return true;
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 == NULL)return false;
return traversal(root, targetSum - root->val);
}
};
精简后:
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if(root == NULL)return false;
if(!root->left && !root->right && targetSum == root->val){
return true;
}
return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum - root->val);
}
};
113.路径总和Ⅱ
题目
给你二叉树的根节点 root
和一个整数目标和 targetSum
,找出所有 从根节点到叶子节点 路径总和等于给定目标和的路径。
叶子节点 是指没有子节点的节点。
示例:
输入:root = [5,4,8,11,null,13,4,7,2,null,null,5,1], targetSum = 22
输出:[[5,4,11,2],[5,8,4,5]]
提示:
-
树中节点总数在范围
[0, 5000]
内 -
-1000 <= Node.val <= 1000
-
-1000 <= targetSum <= 1000
思路
要遍历整个树,找到所有路径,所以递归函数不要返回值!
代码实现
class Solution {
private:
vector<vector<int>> result;
vector<int> path;
void traversal(TreeNode* cur, int count){
if(!cur->left && !cur->right && count == 0){
result.push_back(path);
return;
}
if(!cur->left && !cur->right) return;
if(cur->left){
path.push_back(cur->left->val);
count -= cur->left->val;
traversal(cur->left, count);
count += cur->left->val;
path.pop_back();
}
if(cur->right){
path.push_back(cur->right->val);
count -= cur->right->val;
traversal(cur->right, count);
count += cur->right->val;
path.pop_back();
}
return;
}
public:
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
result.clear();
path.clear();
if(root == NULL)return result;
path.push_back(root->val);
traversal(root, targetSum - root->val);
return result;
}
};
106.从中序与后序遍历序列构造二叉树
题目
给定两个整数数组 inorder
和 postorder
,其中 inorder
是二叉树的中序遍历, postorder
是同一棵树的后序遍历,请你构造并返回这棵 二叉树 。
示例:
输入:inorder = [9,3,15,20,7], postorder = [9,15,7,20,3]
输出:[3,9,20,null,null,15,7]
提示:
-
1 <= inorder.length <= 3000
-
postorder.length == inorder.length
-
-3000 <= inorder[i], postorder[i] <= 3000
-
inorder
和postorder
都由 不同 的值组成 -
postorder
中每一个值都在inorder
中 -
inorder
保证是树的中序遍历 -
postorder
保证是树的后序遍历
思路
如何根据两个顺序构造一个唯一的二叉树:
以 后序数组 的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切,每次后序数组最后一个元素就是节点元素
流程如图:
-
后序数组为 0,空节点
-
后续数组最后一个元素为节点元素
-
寻找中序数组位置作切割点
-
切中序数组,切成中序左数组和中序右数组
-
切后序数组
-
递归处理左区间右区间
框架:
TreeNode* traversal (vector<int>& inorder, vector<int>& postorder) {
// 第一步
if (postorder.size() == 0) return NULL;
// 第二步:后序遍历数组最后一个元素,就是当前的中间节点
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;
}
难点:如何切割,如何找好边界值
💡注意统一区间定义
首先切割中序数组,切割点在后序数组的最后一个元素,用这个元素来切割中序数组,所以必要先切割中序数组。
中序数组相对比较好切,找到切割点(后序数组的最后一个元素)在中序数组的位置,然后切割,坚持左闭右开原则,代码如下:
// 找到中序遍历的切割点
int delimiterIndex;
for(delimiterIndex = 0; delimiterIndex < inorder.size(); delimiterIndex++){
if(inorder[delimiterIndex] == rootValue) break;
}
//左闭右开区间:[0, delimiterIndex)
vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
// [delimiterIndex + 1, end)
vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end());
接下来切割后序数组,后序数组最后一个元素已经用过了。
找切割点通过中序数组大小一定是和后序数组大小相同这个特性,中序数组已经切成了左中序和右中序,那么后序数组也可以按照左中序数组的大小来切割,切成左后序和右后序,代码如下:
// postorder 舍弃末尾元素
postorder.resize(postorder.size() - 1);
//左闭右开,注意这里使用了左中序数组大小作为切割点:[0, leftInorder.size)
vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
//[leftInorder.size(), end)
vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());
中序数组和后序数组都切割完成,开始递归,代码如下:
root->left = traversal(leftInorder, leftPostorder);
root->right = traversal(rightInorder, rightPostorder);
代码实现
-
确定递归函数的参数和返回值:TreeNode* traversal(inorder, postorder){
-
终止条件:
- 后序数组为 0:if(postorder.size = 0) return NULL
- 找到分割点:
- rootvalue = postorder[postorder.size -1]
- 定义节点:root = new TreeNode(rootvalue)
- 特殊情况:if(postorder.size ==1) return root;
- 寻找中序数组位置:
- int index = 0
- for(index = 0; index < inorder.size; index++)
- if(inorder[index] = rootvalue) break;
- 切中序数组:得到左中序、右中序
- 切后序数组:得到左后序、右后序
- root->left = traversal(左中序,左后序)
- root->right = traversal(右中序,右后序)
- return root
class Solution {
private:
TreeNode* traversal(vector<int>& inorder, vector<int>& postorder){
if(postorder.size() == 0)return NULL;
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;
}
vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end());
postorder.resize(postorder.size() - 1);
vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());
root->left = traversal(leftInorder, leftPostorder);
root->right = traversal(rightInorder, rightPostorder);
return root;
}
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if(inorder.size() == 0 || postorder.size() == 0)return NULL;
return traversal(inorder, postorder);
}
};
优化版本
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 NULL;
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 NULL;// 左闭右开的原则
return traversal(inorder, 0, inorder.size(), postorder, 0, postorder.size());}
};
105.从前序与中序遍历序列构造二叉树
题目
给定两个整数数组 preorder
和 inorder
,其中 preorder
是二叉树的先序遍历, inorder
是同一棵树的中序遍历,请构造二叉树并返回其根节点。
示例:
输入: preorder = [3,9,20,15,7], inorder = [9,3,15,20,7]
输出: [3,9,20,null,null,15,7]
提示:
-
1 <= preorder.length <= 3000
-
inorder.length == preorder.length
-
-3000 <= preorder[i], inorder[i] <= 3000
-
preorder
和inorder
均 无重复 元素 -
inorder
均出现在preorder
-
preorder
保证 为二叉树的前序遍历序列 -
inorder
保证 为二叉树的中序遍历序列
思路
思路与 106 一样
代码实现
class Solution {
private:
TreeNode* traversal(vector<int>& preorder, vector<int>& inorder){
if(preorder.size() == 0)return NULL;
int rootValue = preorder[0];
TreeNode* root = new TreeNode(rootValue);
if(preorder.size() == 1)return root;
int delimiterIndex;
for(delimiterIndex = 0; delimiterIndex < inorder.size(); ++delimiterIndex){
if(inorder[delimiterIndex] == rootValue) break;
}
vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end());
preorder.erase(preorder.begin());
vector<int> leftPreorder(preorder.begin(), preorder.begin() + leftInorder.size());
vector<int> rightPreorder(preorder.begin() + leftInorder.size(), preorder.end());
root->left = traversal(leftPreorder, leftInorder);
root->right = traversal(rightPreorder, rightInorder);
return root;
}
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.size() == 0 || inorder.size() == 0) return NULL;
return traversal(preorder, inorder);
}
};
优化版本
class Solution {
private:
TreeNode* traversal(vector<int>& preorder, int preorderBegin, int preorderEnd, vector<int>& inorder, int inorderBegin, int inorderEnd){
if(preorderBegin == preorderEnd) return NULL;
int rootValue = preorder[preorderBegin];
TreeNode* root = new TreeNode(rootValue);
if(preorderEnd - preorderBegin == 1)return root;
int delimiterIndex;
for(delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; ++delimiterIndex){
if(inorder[delimiterIndex] == rootValue) break;
}
int leftInorderBegin = inorderBegin;
int leftInorderEnd = delimiterIndex;
int rightInorderBegin = delimiterIndex + 1;
int rightInorderEnd = inorderEnd;
int leftPreorderBegin = preorderBegin + 1;
int leftPreorderEnd = preorderBegin + 1 + delimiterIndex - inorderBegin;
int rightPreorderBegin = preorderBegin + 1 + (delimiterIndex - inorderBegin);
int rightPreorderEnd = preorderEnd;
root->left = traversal(preorder, leftPreorderBegin, leftPreorderEnd, inorder, leftInorderBegin, leftInorderEnd);
root->right = traversal(preorder, rightPreorderBegin, rightPreorderEnd, inorder, rightInorderBegin, rightInorderEnd);
return root;
}
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.size() == 0 || inorder.size() == 0) return NULL;
return traversal(preorder, 0, preorder.size(), inorder, 0, inorder.size());
}
};