文章目录
一、404.左叶子之和
注意点:
- 是左叶子,不是左节点!!只有当前遍历的节点是父节点,才能判断其子节点是不是左叶子。
- 左叶子值为0的情况,空节点,左叶子值为0;当前遍历的节点是叶子节点,左叶子值为0。
递归法-前序遍历-找到左叶子
class Solution {
public:
//递归法
int sumOfLeftLeaves(TreeNode* root) {
//递归结束条件 空节点;叶子节点
if(root==nullptr) return 0;
if(root->left==nullptr && root->right==nullptr) return 0;
int leftvalue = sumOfLeftLeaves(root->left);//左
//找到左叶子 记录值
if(root->left && root->left->left==nullptr && root->left->right==nullptr) leftvalue = root->left->val;
int rightvalue = sumOfLeftLeaves(root->right);//右
int sum = leftvalue+rightvalue;//中
return sum;
}
};
迭代法 栈
class Solution {
public:
//迭代法
int sumOfLeftLeaves(TreeNode* root)
{
stack<TreeNode*> st;
int sum = 0;
if(root==nullptr) return 0;//空树不如栈
st.push(root);
while(!st.empty())
{
TreeNode* cur = st.top();
st.pop();
if(cur->left && !cur->left->left && !cur->left->right) sum += cur->left->val;
if(cur->left) st.push(cur->left);
if(cur->right) st.push(cur->right);
}
return sum;
}
};
二、513. 找树左下角的值
class Solution {
public:
int depth = 0;
int leftvalue = 0;
//版本1
/*void maxdepth(TreeNode* cur, int level)
{
if(cur==nullptr) return;
if(!cur->left && !cur->right && level==1) leftvalue = cur->val;//只有根节点
if(level>depth && cur->left)//有左叶子
{
leftvalue = cur->left->val;
depth = level;
}
if(level>depth && !cur->left)//没有左叶子
{
leftvalue = cur->val;
depth = level;
}
if(cur->left) maxdepth(cur->left, level+1);
if(cur->right) maxdepth(cur->right, level+1);
}*/
//优化 找到最底层最左节点放在递归结束里边
void traversal(TreeNode* cur, int level)
{
if(cur->left==nullptr && cur->right==nullptr)
{
if(level>depth)
{
depth = level;
leftvalue = cur->val;
}
return;
}
if(cur->left) traversal(cur->left, level+1);
if(cur->right) traversal(cur->right, level+1);
return;
}
int findBottomLeftValue(TreeNode* root) {
//版本1
//if(root==nullptr) return 0;
//maxdepth(root, 1);
//优化
traversal(root, 1);
return leftvalue;
}
};
三、112. 路径总和
递归法 回溯
注意递归的返回值和参数,回溯的操作
- 递归参数,不用加法,累加来判断和与目标值是否相等比较麻烦,用减法比较简单
- 递归返回值,这里递归需要返回值,返回布尔类型
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值——113.路径总和ii
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值——236. 二叉树的最近公共祖先
- 如果要搜索其中一条符合条件的路径,递归一定需要返回值,因为遇到符合条件的路径了就要及时返回——本题
class Solution {
public:
//递归
bool traversal(TreeNode* cur, int count)
{
//递归结束
if(cur->left==nullptr && cur->right==nullptr && count==0) return true;//找到叶子节点,正好找到路径
if(cur->left==nullptr && cur->right==nullptr) 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;
}
bool hasPathSum(TreeNode* root, int targetSum) {
if(!root) return false;
return traversal(root, targetSum - root->val);
}
};
递归-单次操作优化
class Solution {
public:
//递归
bool traversal(TreeNode* cur, int count)
{
//递归结束
if(cur->left==nullptr && cur->right==nullptr && count==0) return true;//找到叶子节点,正好找到路径
if(cur->left==nullptr && cur->right==nullptr) return false;//找到叶子节点,没有找到符合条件的路径
//单次操作
if(cur->left) if(traversal(cur->left, count-cur->left->val)) return true;
if(cur->right) if(traversal(cur->right, count-cur->right->val)) return true;
return false;
}
bool hasPathSum(TreeNode* root, int targetSum) {
if(!root) return false;
return traversal(root, targetSum - root->val);
}
};
四、106.从中序与后序遍历序列构造二叉树
- 以后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
- 切割
第一步:如果数组大小为零的话,说明是空节点了。
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
第五步:切割后序数组,切成后序左数组和后序右数组
第六步:递归处理左区间和右区间
切割过程中,保证左闭右开
class Solution {
public:
TreeNode* traversal(vector<int>& inorder, vector<int>& postorder)
{
if(postorder.size()==0) return nullptr;
//1.根节点就是后序数组的最后一个元素
int rootvalue = postorder[postorder.size() - 1];
//2.创建二叉树
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;
}
//3.切割中序数组 左闭右开 [0, delimiterIndex)
vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
//3.切割中序数组 左闭右开 [delimiterIndex+1, end)
vector<int> rightIndoer(inorder.begin() + delimiterIndex + 1, inorder.end());
//4.切割后序数组 舍弃末尾元素(根节点)
postorder.resize(postorder.size() - 1);
//5.使用左中序数组大小作为切割点 后序左数组和后序右数组大小和中序左数组和中序右数组一样
//左闭右开 [0, leftInorder.size)
vector<int> leftPostorder(postorder.begin(), postorder.begin() + leftInorder.size());
//左闭右开 [leftInorder.size, end)
vector<int> rightPostorder(postorder.begin() + leftInorder.size(), postorder.end());
//6.递归
root->left = traversal(leftInorder, leftPostorder);
root->right = traversal(rightIndoer, rightPostorder);
return root;
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if(inorder.size()==0 || postorder.size()==0) return NULL;
return traversal(inorder, postorder);
}
};
五、相关题-105.从前序与中序遍历序列构造二叉树
思路和106的一样,不同的是前序数组resize的地方
class Solution {
public:
TreeNode* traversal(vector<int>& preorder, vector<int>& inorder)
{
if(preorder.size()==0) return nullptr;
//1.根节点就是前序数组的第一个元素
int rootvalue = preorder[0];
//2.创建二叉树
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;
}
//3.切割中序数组 左闭右开 [0, delimiterIndex)
vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
//3.切割中序数组 左闭右开 [delimiterIndex+1, end)
vector<int> rightIndoer(inorder.begin() + delimiterIndex + 1, inorder.end());
//4.切割前序数组 舍弃头部元素(根节点)
preorder.assign(preorder.begin()+1, preorder.end());
//5.使用左中序数组大小作为切割点 前序左数组和前序右数组大小和中序左数组和中序右数组一样
//左闭右开 [0, leftInorder.size)
vector<int> leftPreorder(preorder.begin(), preorder.begin() + leftInorder.size());
//左闭右开 [leftInorder.size, end)
vector<int> rightPreorder(preorder.begin() + leftInorder.size(), preorder.end());
//6.递归
root->left = traversal(leftPreorder, leftInorder);
root->right = traversal(rightPreorder, rightIndoer);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if(preorder.size()==0 || inorder.size()==0) return NULL;
return traversal(preorder, inorder);
}
};
六、654.最大二叉树
构造树-前序遍历-递归:最大值构造中间节点,最大值左区间构造左子树,最大值右区间构造右子树
不允许空节点进入递归,遇到叶子节点就终止递归
class Solution {
public:
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
TreeNode* node = new TreeNode(0);//创建新节点,数组的值赋给节点,构造树
//1.递归条件 数组只有一个数说明遍历到叶子节点了
if(nums.size()==1)
{
node->val = nums[0];//数组的值赋给节点
return node;
}
//2.找最大值作为根节点 划分左右区间
int maxValue = 0;
int maxIndex = 0;
for(int i=0;i<nums.size();i++)
{
if(nums[i]>maxValue)
{
maxValue = nums[i];
maxIndex = i;
}
}
node->val = maxValue;//中
//3.最大值左区间 作为左子树 递归 要保证左区间至少有一个数
if(maxIndex > 0)
{
vector<int> tree(nums.begin(), nums.begin()+maxIndex);
node->left = constructMaximumBinaryTree(tree);//左
}
//4.最大值右区间 作为右子树 递归 要保证右区间至少有一个数
if(maxIndex < (nums.size()-1))
{
vector<int> tree(nums.begin()+ maxIndex + 1, nums.end());
node->right = constructMaximumBinaryTree(tree);//右
}
return node;
}
};
优化,思路一样,还是前序遍历-递归:最大值构造中间节点,最大值左区间构造左子树,最大值右区间构造右子树
但是允许空节点进入递归,遇到空节点,也就是数组区间为0,就终止递归
class Solution {
public:
TreeNode* traversal(vector<int>& nums, int left, int right)
{
if(left >= right) return nullptr;//数组区间为0,遇到空节点,递归结束
//划分区间 找到最大值的位置
int maxIndex = left;
for(int i=left; i<right; i++)
{
if(nums[i]>nums[maxIndex]) maxIndex = i;
}
TreeNode* node = new TreeNode(nums[maxIndex]);//中
node->left = traversal(nums, left, maxIndex);//左 保证区间左闭右开 [left, maxValueIndex)
node->right = traversal(nums, maxIndex+1, right);//右 保证区间左闭右开 [maxValueIndex + 1, right)
return node;
}
TreeNode* constructMaximumBinaryTree(vector<int>& nums)
{
return traversal(nums, 0, nums.size());
}
};
总结
题一:通过节点的父节点以及孩子节点来判断本节点的属性
题二:找树的最后一行的最左边的值,深度最大的叶子节点一定是最后一行
题三:一般情况下,如果需要搜索整棵二叉树,那么递归函数就不要返回值,如果要搜索其中一条符合条件的路径,递归函数就需要返回值,因为遇到符合条件的路径了就要及时返回
题四:构造二叉树注意点
- 分割时候,坚持区间不变量原则,左闭右开,或者左闭又闭
- 分割的时候,注意后序/前序已经有一个节点作为中间节点了,不能继续使用了
- 如何使用切割后的后序/前序数组来切合中序数组?利用中序数组大小一定是和后序/前序数组的大小相同这一特点来进行切割
- 前序和后序不能唯一构成一棵二叉树,因为没有中序遍历就无法确定左右部分,也就无法分割
题五:
- 注意类似用数组构造二叉树的题目,每次分隔尽量不要定义新的数组,而是通过下标索引直接在原数组上操作,这样可以节约时间和空间上的开销。
- 一般情况来说,如果让空节点(空指针)进入递归,就不加if,如果不让空节点进入递归,就加if限制一下, 终止条件也会相应的调整。