资料来源:代码随想录
15.左叶子之和 404
无法直接判断某个节点是不是左叶子,只能判断是不是叶子,必须要通过该节点的父节点来判断其左孩子是不是左叶子。
递归法:
递归的遍历顺序为后序遍历(左右中),是因为要通过递归函数的返回值来累加求取左叶子数值之和。
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
//终止条件:
if(root==NULL) return 0; //空节点左叶子必为0
if(root->left==NULL && root->right==NULL) return 0; //已经访问到叶子节点了,叶子节点没有孩子,所以其左叶子必为0
//单层递归逻辑
int leftValue=sumOfLeftLeaves(root->left); //递归左节点,这里是把根节点的左孩子当做一个新的根节点传入,求根节点的左子树的左叶子之和
//求值逻辑
if(root->left!=NULL && root->left->left==NULL && root->left->right==NULL) //根节点的左节点不为空,但左节点的左右孩子均为空,说明这个左节点是左叶子节点,记录一下
{
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;
if(root==NULL) return 0;
st.push(root);
int result=0;
while(!st.empty())
{
TreeNode* node=st.top();
st.pop();
//统计左叶子节点并累加
if(node->left!=NULL && node->left->left==NULL && node->left->right==NULL)
{
result=result+node->left->val;
}
//注意顺序:
if(node->right) st.push(node->right);
if(node->left) st.push(node->left);
}
return result;
}
};
16.找树左下角的值 513
递归:
一直向左遍历是不对的,因为最左边的值未必在最后一行,要首先保证是最后一行,然后再找最左边的。
思路:寻找深度最大的叶子节点,优先向左遍历,以此找到最后一行最左的值。前中后序都可以,因为本题没有中间的处理逻辑,所以保证左优先即可。
class Solution {
public:
//定义两个全局变量,这样接下来的递归函数修改的就是全局变量,那么就不需要返回值了
int maxDepth=INT_MIN; //最大深度
int result; //存在树左下角的值
//递归
void traversal(TreeNode* root, int depth) //传入一个变量用来记录深度
{
//终止条件:遍历到叶子节点就更新最大深度,并记录数值
if(root->left==NULL && root->right==NULL)
{
if(maxDepth<depth)
{
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; //因为是全局变量,所以在这可以直接返回
}
};
要回溯是因为深度必须-1,返回到上一层节点,才能继续往另一边遍历。
回溯可以精简一下:递归时传入的深度是depth+1,等于没有改变depth这个变量的值,也就不需要+1再-1了。
if(root->left)
{
traversal(root->left,depth+1);
}
if(root->right)
{
traversal(root->right,depth+1);
}
迭代:
层序遍历,记录最后一层第一个节点数值即可。
从左向右遍历,每一层的第一个节点必是该层的最左节点。如何实现“最后一层”?每迭代一层,结果更新一次,最后返回的就是最后一层。
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
queue<TreeNode*> que;
int result=0;
if(root!=NULL) que.push(root);
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;
}
};
17.路径总和 112
递归:
1.函数参数及返回值:
参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。
再来看返回值,递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:
- 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)
- 如果需要搜索整棵二叉树且需要处理递归返回值,递归函数就需要返回值。 (这种情况我们在236. 二叉树的最近公共祖先 (opens new window)中介绍)
- 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(本题的情况)
2.终止条件
计数器如何统计这一条路径的和呢?
不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。
如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
如果遍历到了叶子节点,count不为0,就是没找到。
3.单层递归逻辑
首先判断节点是否为空,不要让空节点进入递归。
以左节点为例。目的是找传入的节点的左子树中是否有符合条件的路径,往下走一步就要用count减掉这一个节点的值。递归左子树也是会有返回值的,可以直接对这个返回值进行处理,如果这个返回值为真的话,说明在左子树中找到了符合条件的路径,那么整体就可以直接返回了,所以return TRUE。递归结束后,为了能够后撤并重新开始,需要回溯,把减掉的值再加上。
class Solution {
public:
//递归函数
bool traversal(TreeNode* cur, int count) //传入的参数包括一个二叉树根节点和计数器,因为要搜索一条符合条件的路径,找到符合条件的要立即返回,所以有返回值
{
//终止条件:
//到叶子节点了,且计数器减到0,则说明找到了符合要求的路径;计数器没有减到0,说明没找到
if(cur->left==NULL && cur->right==NULL && count==0) return true;
if(cur->left==NULL && cur->right==NULL && count!=0) return false;
//单层递归逻辑:没有中间处理节点,所以前中后序都可以
if(cur->left)
{
count=count-cur->left->val;
if(traversal(cur->left,count)) return true;
count=count+cur->left->val; //回溯,后撤,以继续找符合条件的路径
}
if(cur->right)
{
count=count-cur->right->val;
if(traversal(cur->right,count)) return true;
count=count+cur->right->val;
}
return false; //以上都不成立,说明左右都没找到
}
bool hasPathSum(TreeNode* root, int targetSum) {
if(root==NULL) return false;
bool result=traversal(root,targetSum-root->val);
return result;
}
};
回溯的过程还可以精简一下,让count的值不要随着递归被改变就行,所以直接传入减过的参数。
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;
}
迭代:
如果使用栈模拟递归的话。
此时栈里一个元素不仅要记录该节点指针,还要记录从头结点到该节点的路径数值总和。
c++就用pair结构来存放这个栈里的元素。
定义为:pair<TreeNode*, int>
pair<节点指针,路径数值总和>
这个为栈里的一个元素。
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if(root==NULL) return false;
//栈内存放元素:
stack<pair<TreeNode*, int>> st; //pair<节点指针,路径节点数值之和>
st.push(pair<TreeNode*,int>(root,root->val));
while(!st.empty()) //栈迭代不需要size
{
pair<TreeNode*,int> node=st.top();
st.pop();
//到达叶子节点,且节点数值之和为sum
if(node.first->left==NULL && node.first->right==NULL && node.second==targetSum) return true;
//压入右节点,并更新路径节点数值之和,因为是栈,所以右边先入栈,保证前序
if(node.first->right)
{
st.push(pair<TreeNode*,int>(node.first->right,node.second+node.first->right->val));
}
//压入左节点,并更新路径节点数值之和
if(node.first->left)
{
st.push(pair<TreeNode*,int>(node.first->left,node.second+node.first->left->val));
}
}
//迭代完了也没找到
return false;
}
};
113 路径总和
要找到符合条件的所有路径,所以要遍历整棵二叉树,则不需要返回值。
class Solution {
private:
//因为要遍历整棵树,所以递归函数没有返回值,那么最终需要的结果就应该被定义为全局变量
vector<vector<int>> result; //要存好几条路径,所以结果是二维数组
vector<int> path; //单条路径
//递归函数,没有返回值
//思路也是从要求的目标值开始减,在叶子节点减到0说明找到了
void traversal(TreeNode* cur, int count)
{
//终止条件:找到和没找到两种,都返回
if(cur->left==NULL && cur->right==NULL && count==0)
{
result.push_back(path); //找到了,把这条路径存起来
return;
}
if(cur->left==NULL && cur->right==NULL) //到了叶子节点但count没有到0,说明没找到,直接返回
{
return;
}
//单层递归逻辑,只有左右
if(cur->left) //空节点不进递归
{
path.push_back(cur->left->val); //把节点放进路径里面
count=count-cur->left->val;
traversal(cur->left,count); //向左边递归,寻找左边是否有符合要求的路径
count=count+cur->left->val; //回溯:不管前面的向左递归有没有找到符合要求的路径,递归结束后都要回退到前面,以重新开始寻找新的路径
path.pop_back(); //回溯:要回退到前面,就要把加进path的节点都弹出来
}
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; //这为什么要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;
}
};
18.从中序和后序遍历序列构造二叉树 106
思路分几步:
-
第一步:如果数组大小为零的话,说明是空节点了。
-
第二步:如果不为空,那么取后序数组最后一个元素作为中间节点元素。
-
第三步:找到后序数组的中间节点元素在中序数组的位置,作为切割点。
-
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
-
第五步:用切割出来的中序左数组切割后序数组,切成后序左数组和后序右数组
-
第六步:递归处理左区间和右区间,即分成中序左数组、后序左数组去处理左边,中序右数组、后序右数组去处理右边
切割的时候左闭右开是怎么实现的?vector默认左闭右开。按照这个原则去设置数组范围即可。
class Solution {
public:
//先进行递归
//参数及返回值:传入中序后序数组,要构建起一个二叉树,所以返回二叉树根节点
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 index;
for(index=0; index<inorder.size(); index++)
{
if(inorder[index]==rootValue) break;
}
//切割中序数组,得到中序左数组和中序右数组
//注意是左闭右开,但我没懂这是咋体现出来的
//中序左数组:[0,inorder.begin()+index)
vector<int> leftInorder(inorder.begin(),inorder.begin()+index);
//中序右数组:[inorder.begin()+index+1,inorder.end()) 因为要把中间的根节点空开,所以是+1
vector<int> rightInorder(inorder.begin()+index+1,inorder.end());
//切割后序数组之前要先改一下数组大小,因为最后一个元素已经用过了
postorder.resize(postorder.size()-1);
//切割后序数组,得到后序左数组和后序右数组
//后序左数组:[0,postorder.begin()+index)
vector<int> leftPostorder(postorder.begin(),postorder.begin()+index);
//后序右数组:[postorder.begin()+index,postorder.end())
vector<int> rightPostorder(postorder.begin()+index,postorder.end());
//向左向右递归,继续切割:传入中序后序数组,返回对应根节点
root->left=traversal(leftInorder,leftPostorder); //左中序、左后序
root->right=traversal(rightInorder,rightPostorder); //右中序、右后序
return root;
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if(inorder.size()==0 || postorder.size()==0) return NULL;
return traversal(inorder,postorder);
}
};
下标索引法:这个方法是直接利用下标在原数组上操作,可以不用每次都构建新数组,极大地节省了时间和空间。
class Solution {
private:
TreeNode* traversal(vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& postorder, int postorderBegin, int postorderEnd)
{
if(postorderEnd==postorderBegin) return NULL;
//构造中间根节点
int rootValue=postorder[postorderEnd-1]; //因为传入的postoederEnd是后序数组的size,所以要访问最后一个元素的话,就要-1
TreeNode* root=new TreeNode(rootValue);
if(postorderEnd-postorderBegin==1) return root;
//在中序数组中寻找根节点
int index=0;
for(index=inorderBegin; index<inorderEnd; index++)
{
if(inorder[index]==rootValue) break;
}
//切割中序数组
//中序左数组
int leftInorderBegin=inorderBegin;
int leftInorderEnd=index;
//中序右数组
int rightInorderBegin=index+1;
int rightInorderEnd=inorderEnd; //左闭右开的区间,所以不包含inorderEnd,没有超
//切割后序数组
//后序左数组
int leftPostorderBegin=postorderBegin;
int leftPostorderEnd=postorderBegin+(index-inorderBegin); //后序左数组结尾=开头+中序左数组的size
//后序右数组
int rightPostorderBegin=postorderBegin+(index-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()); //左闭右开区间,不包含第size个元素
}
};
如果使用构建新数组的方法,那么循环的起始和终止可以是0和数组的size();但如果使用下标索引的话,循环的起始和终止必须使用数组的起始和终止下标。
105.用前序和中序构造二叉树
class Solution {
private:
TreeNode* traversal(vector<int>& preorder, int preorderBegin, int preorderEnd, vector<int>& inorder, int inorderBegin, int inorderEnd)
{
//终止条件
if(preorderEnd-preorderBegin==0) return NULL;
//构造中间根节点:前序数组的第一个值
int rootValue=preorder[preorderBegin];
TreeNode* root=new TreeNode(rootValue);
//如果前序数组只有一个节点,那这个就是根节点
if(preorderEnd-preorderBegin==1) return root;
//在中序数组中寻找根节点,准备切割
int index;
for(index=inorderBegin; index<inorderEnd; index++)
{
if(inorder[index]==rootValue) break;
}
//切割中序数组
//中序左数组
int leftInorderBegin=inorderBegin;
int leftInorderEnd=index;
//中序右数组
int rightInorderBegin=index+1;
int rightInorderEnd=inorderEnd;
//切割前序数组
//前序左数组
int leftPreorderBegin=preorderBegin+1; //跳过第一个元素
int leftPreorderEnd=preorderBegin+1+(index-inorderBegin); //加上的是中序左数组的那个size
//前序右数组
int rightPreorderBegin=preorderBegin+1+(index-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());
}
};
19.最大二叉树 654
构造二叉树一定是:前序遍历。
优化版本:使用下标索引,并且允许空数组进入递归,则不用在递归前进行判断
private:
//递归构造二叉树
//左闭右开区间[left,right)
TreeNode* traversal(vector<int>& nums, int left, int right)
{
//终止条件:
if(left>=right) return NULL;
//单层递归逻辑:中间节点
//寻找数组中的最大值,作为根节点;同时用其下标作为分割点
int maxValue=0;
int index=left;
for(int i=left; i<right; i++)
{
if(nums[i]>maxValue)
{
maxValue=nums[i];
index=i;
}
}
//构造根节点
TreeNode* root=new TreeNode(0);
root->val=maxValue;
//向左递归
int leftBegin=left;
int leftEnd=index;
root->left=traversal(nums,leftBegin,leftEnd); //递归得到左子树的根节点
//向右递归
int rightBegin=index+1;
int rightEnd=right;
root->right=traversal(nums,rightBegin,rightEnd);
return root;
}
public:
TreeNode* constructMaximumBinaryTree(vector<int>& nums) {
return traversal(nums,0,nums.size());
}
};