零. 大纲:
参考
一. 深度 & 高度
-
深度:根节点 到该节点的最长简单路径边的条数。
前序遍历(中左右) -
高度:该节点到 叶子节点 的最长简单路径边的条数
后序遍历(左右中)
根节点的高度就是二叉树的最大深度
例子见: [104.二叉树的最大深度]
1.1 104.二叉树的最大深度
- 深度:根节点 到该节点的最长简单路径边的条数。
前序遍历(中左右) - 高度:该节点 到 叶子节点 的最长简单路径边的条数
后序遍历(左右中)
根节点的高度就是二叉树的最大深度,
递归法
根节点的高度就是二叉树的最大深度,
所以本题中我们通过后序求的根节点高度来求的二叉树最大深度
后序 求 高度
// 1.确定递归函数的参数和返回值:参数就是传入树的根节点,
// 返回就返回这棵树的深度,所以返回值为int类型。
// int getdepth(treenode* node)
// 2. 确定终止条件:如果为空节点的话,就返回0,表示高度为0。
// if (node == NULL) return 0;
// 3. 确定单层递归的逻辑:先求它的左子树的深度,再求右子树的深度,
// 最后取左右深度最大的数值 再+1
// (加1是因为算上当前中间节点)就是目前节点为根节点的树的深度。
class solution {
public:
int getdepth(treenode* node) {
if (node == NULL) return 0;
int leftdepth = getdepth(node->left); // 左
int rightdepth = getdepth(node->right); // 右
int depth = 1 + max(leftdepth, rightdepth); // 中
return depth;
}
int maxdepth(treenode* root) {
return getdepth(root);
}
};
//精简之后
// int maxdepth(treenode* root) {
// if (root == null) return 0;
// return 1 + max(maxdepth(root->left), maxdepth(root->right));
// }
前序 求 深度
class solution {
public:
int result;
void getdepth(treenode* node, int depth) {
result = depth > result ? depth : result; // 中
if (node->left == NULL && node->right == NULL) return ;
if (node->left) { // 左
depth++; // 深度+1
getdepth(node->left, depth);
depth--; // 回溯,深度-1
}
if (node->right) { // 右
depth++; // 深度+1
getdepth(node->right, depth);
depth--; // 回溯,深度-1
}
return ;
}
int maxdepth(treenode* root) {
result = 0;
if (root == NULL) return result;
getdepth(root, 1);
return result;
}
};
迭代法: 层序遍历
class solution {
public:
int maxdepth(treenode* root) {
if (root == NULL) return 0;
int depth = 0;
queue<treenode*> que;
que.push(root);
while(!que.empty()) {
int size = que.size();
depth++; // 记录深度
for (int i = 0; i < size; i++) {
treenode* node = que.front();
que.pop();
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
}
return depth;
}
};
559. N 叉树的最大深度
- N叉树 与 二叉树区别:
二叉树:TreeNode* node
node -> left
node -> right
N叉树: 子节点不止两个
递归法,深度优先搜索
class Solution {
public:
int maxDepth(Node* root) {
if (root == nullptr) {
return 0;
}
int maxChildDepth = 0;
vector<Node *> children = root->children;
for (auto child : children) {
int childDepth = maxDepth(child);
maxChildDepth = max(maxChildDepth, childDepth);
}
return maxChildDepth + 1;
}
};
// 精简版本
int maxDepth(Node* root) {
if (root == 0) return 0;
int depth = 0;
for (int i = 0; i < root->children.size(); i++) {
depth = max (depth, maxDepth(root->children[i]));
// depth = (maxDepth(root->children[i]) > depth ?
// maxDepth(root->children[i]) : depth );
// 超时
}
return depth + 1;
}
迭代法,广度优先搜索
class Solution {
public:
int maxDepth(Node* root) {
if (root == nullptr) {
return 0;
}
queue<Node *> qu;
qu.push(root);
int ans = 0;
while (!qu.empty()) {
int size = qu.size();
while (size > 0) {
Node * node = qu.front();
qu.pop();
vector<Node *> children = node->children;
for (auto child : children) {
qu.push(child);
}
size--;
}
ans++;
}
return ans;
}
};
1.2 111. 二叉树的最小深度
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
递归法
class Solution {
public:
int getDepth(TreeNode* node) {
if (node == NULL) return 0;
int leftDepth = getDepth(node->left); // 左
int rightDepth = getDepth(node->right); // 右
// 中
// 当一个左子树为空,右不为空,这时并不是最低点
if (node->left == NULL && node->right != NULL) {
return 1 + rightDepth;
}
// 当一个右子树为空,左不为空,这时并不是最低点
if (node->left != NULL && node->right == NULL) {
return 1 + leftDepth;
}
int result = 1 + min(leftDepth, rightDepth);
return result;
}
int minDepth(TreeNode* root) {
return getDepth(root);
}
};
迭代法
class Solution {
public:
int minDepth(TreeNode* root) {
if(root==NULL) return 0;
int count=0;
queue<TreeNode*> que;
que.push(root);
while(!que.empty()){
int size = que.size();
count++;
for(int i=0; i<size;i++){
TreeNode* node = que.front();
que.pop();
//if(!(node->left || node->right )) return count;
if (!node->left && !node->right) return count;
if(node->left) que.push(node->left);
if(node->right) que.push(node->right);
}
}
return count;
}
};
二. 101.对称二叉树
递归法
class Solution {
public:
bool compare(TreeNode* left, TreeNode* right){
if(left == NULL && right != NULL) return false;
else if(left != NULL && right == NULL ) return false;
else if(left == NULL && right == NULL ) return true;
else if(left->val != right->val ) return false;
// 此时就是:左右节点都不为空,且数值相同的情况
// 此时才做递归,做下一层的判断
bool outside = compare(left->left, right->right);
bool inside = compare(left->right, right->left);
bool isSame = outside && inside;
return isSame;
}
bool isSymmetric(TreeNode* root) {
if(root == NULL ) return true;
return compare(root->left, root->right);
}
};
迭代法
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if (root == NULL) return true;
queue<TreeNode*> que;
que.push(root->left); // 将左子树头结点加入队列
que.push(root->right); // 将右子树头结点加入队列
while (!que.empty()) { // 接下来就要判断这两个树是否相互翻转
TreeNode* leftNode = que.front(); que.pop();
TreeNode* rightNode = que.front(); que.pop();
if (!leftNode && !rightNode) { // 左节点为空、右节点为空,此时说明是对称的
continue;
}
// 左右一个节点不为空,或者都不为空但数值不相同,返回false
if ((!leftNode || !rightNode || (leftNode->val != rightNode->val))) {
return false;
}
// outside
que.push(leftNode->left); // 加入左节点左孩子
que.push(rightNode->right); // 加入右节点右孩子
// inside
que.push(leftNode->right); // 加入左节点右孩子
que.push(rightNode->left); // 加入右节点左孩子
}
return true;
}
};
572.另一个树的子树
参考:【简单明晰】C++ 判断子树
如果root所在树和subRoot所在树完全相同,则直接返回true。
否则,就分别判断root的左子树和右子树是否和subRoot所在树完全相同。
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if (p == nullptr && q == nullptr)
return true;
if (p && q && p->val == q->val)
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
else
return false;
}
bool isSubtree(TreeNode* root, TreeNode* subRoot) {
if (!root) //root不可能走到空
return false;
if (isSameTree(root, subRoot))
return true;
return isSubtree(root->left, subRoot) || isSubtree(root->right, subRoot); //左边为真就不用求右边了,所以是或关系
}
};
100. 相同的树
深度优先搜索
如果两个二叉树都为空,则两个二叉树相同。如果两个二叉树中有且只有一个为空,则两个二叉树一定不相同。
如果两个二叉树都不为空,那么首先判断它们的根节点的值是否相同,若不相同则两个二叉树一定不同,若相同,再分别判断两个二叉树的左子树是否相同以及右子树是否相同。这是一个递归的过程,因此可以使用深度优先搜索,递归地判断两个二叉树是否相同。
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if (p == nullptr && q == nullptr) {
return true;
} else if (p == nullptr || q == nullptr) {
return false;
} else if (p->val != q->val) {
return false;
} else {
return isSameTree(p->left, q->left) && isSameTree(p->right, q->right);
}
}
};
三. 222. 完全二叉树的节点个数
当普通二叉树处理
递归法
时间复杂度:O(n)
空间复杂度:O(log n),算上了递归系统栈占用的空间
class Solution {
private:
int getNodesNum(TreeNode* cur) {
if (cur == NULL) return 0;
int leftNum = getNodesNum(cur->left); // 左
int rightNum = getNodesNum(cur->right); // 右
int treeNum = leftNum + rightNum + 1; // 中
return treeNum;
}
public:
int countNodes(TreeNode* root) {
return getNodesNum(root);
}
};
迭代法
时间复杂度:O(n)
空间复杂度:O(n)
class Solution {
public:
int countNodes(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) 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();
result++; // 记录节点数量
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
}
return result;
}
};
利用完全二叉树特点
- 完全二叉树只有两种情况,情况一:就是满二叉树,情况二:最后一层叶子节点没有满。
对于情况一,可以直接用 2^树深度 - 1 来计算,注意这里根节点深度为1。
对于情况二,分别递归左孩子,和右孩子,递归到某一深度一定会有左孩子或者右孩子为满二叉树,然后依然可以按照情况1来计算。
时间复杂度:O(log n × log n)
空间复杂度:O(log n)
class Solution {
public:
int countNodes(TreeNode* root) {
if (root == nullptr) return 0;
TreeNode* left = root->left;
TreeNode* right = root->right;
int leftDepth = 0, rightDepth = 0; // 这里初始为0是有目的的,为了下面求指数方便
while (left) { // 求左子树深度
left = left->left;
leftDepth++;
}
while (right) { // 求右子树深度
right = right->right;
rightDepth++;
}
if (leftDepth == rightDepth) {
return (2 << leftDepth) - 1; // 注意(2<<1) 相当于2^2,所以leftDepth初始为0
}
return countNodes(root->left) + countNodes(root->right) + 1;
}
};
理解:
四. 110. 平衡二叉树
给定一个二叉树,判断它是否是高度平衡的二叉树。
一棵高度平衡二叉树定义为:
一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1 。
递归
class Solution {
public:
// 返回以该节点为根节点的二叉树的高度,如果不是平衡二叉树了则返回-1
int getHeight(TreeNode* node) {
if (node == NULL) {
return 0;
}
int leftHeight = getHeight(node->left); // 左
if (leftHeight == -1) return -1;
int rightHeight = getHeight(node->right); // 右
if (rightHeight == -1) return -1;
int result;
if ( abs(leftHeight - rightHeight) > 1 ) { // 中
result = -1;
} else {
result = 1 + max(leftHeight, rightHeight); // 以当前节点为根节点的树的最大高度
}
return result;
}
bool isBalanced(TreeNode* root) {
return getHeight(root) == -1 ? false : true ;
}
};
// int leftHeight = getHeight(node->left);
// if (leftHeight == -1) return -1;
// int rightHeight = getHeight(node->right);
// if (rightHeight == -1) return -1;
// return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
迭代法 效率较低
可以使用层序遍历来求深度,但是就不能直接用层序遍历来求高度了,这就体现出求高度和求深度的不同。
-
效率低:
因为没有很好的模拟回溯的过程,所以迭代法有很多重复的计算。
虽然理论上所有的递归都可以用迭代来实现,但是有的场景难度可能比较大。
例如:都知道回溯法其实就是递归,但是很少人用迭代的方式去实现回溯算法!
因为对于回溯算法已经是非常复杂的递归了,如果再用迭代的话,就是自己给自己找麻烦,效率也并不一定高。 -
本题的迭代方式可以先定义一个函数,专门用来求高度。
这个函数通过栈模拟的后序遍历找每一个节点的高度(其实是通过求传入节点为根节点的最大深度来求的高度) -
然后再用栈来模拟后序遍历,遍历每一个节点的时候,再去判断左右孩子的高度是否符合
class Solution {
private:
int getDepth(TreeNode* cur) {
stack<TreeNode*> st;
if (cur != NULL) st.push(cur);
int depth = 0; // 记录深度
int result = 0;
while (!st.empty()) {
TreeNode* node = st.top();
if (node != NULL) {
st.pop();
st.push(node); // 中
st.push(NULL);
depth++;
if (node->right) st.push(node->right); // 右
if (node->left) st.push(node->left); // 左
} else {
st.pop();
node = st.top();
st.pop();
depth--;
}
result = result > depth ? result : depth;
}
return result;
}
public:
bool isBalanced(TreeNode* root) {
stack<TreeNode*> st;
if (root == NULL) return true;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top(); // 中
st.pop();
if (abs(getDepth(node->left) - getDepth(node->right)) > 1) {
return false;
}
if (node->right) st.push(node->right); // 右(空节点不入栈)
if (node->left) st.push(node->left); // 左(空节点不入栈)
}
return true;
}
};
五. 路径问题
参考:
- 代码随想录
- 一篇文章解决所有二叉树路径问题(问题分析+分类模板+题目剖析)
作者:eh-xing-qing
来源:力扣(LeetCode)
问题分类
-
1、自顶向下:
顾名思义,就是从某一个节点(不一定是根节点),从上向下寻找路径,到某一个节点(不一定是叶节点)结束
具体题目如下:
257. 二叉树的所有路径
面试题 04.12. 求和路径
112. 路径总和
113. 路径总和 II
437. 路径总和 III
988. 从叶结点开始的最小字符串
而继续细分的话还可以分成一般路径与给定和的路径 -
2、非自顶向下:
就是从任意节点到任意节点的路径,不需要自顶向下
124. 二叉树中的最大路径和
687. 最长同值路径
543. 二叉树的直径
解题模板
这类题通常用深度优先搜索(DFS)和广度优先搜索(BFS)解决,BFS较DFS繁琐,这里为了简洁只展现DFS代码
一、自顶而下:
dfs
一般路径:
vector<vector<int>>res;
void dfs(TreeNode*root,vector<int>path)
{
if(!root) return; //根节点为空直接返回
path.push_back(root->val); //作出选择
if(!root->left && !root->right) //如果到叶节点
{
res.push_back(path);
return;
}
dfs(root->left,path); //继续递归
dfs(root->right,path);
}
给定和的路径:
void dfs(TreeNode*root, int sum, vector<int> path)
{
if (!root)
return;
sum -= root->val;
path.push_back(root->val);
if (!root->left && !root->right && sum == 0)
{
res.push_back(path);
return;
}
dfs(root->left, sum, path);
dfs(root->right, sum, path);
}
这类题型DFS注意点:
-
1、如果是找路径和等于给定target的路径的,那么可以不用新增一个临时变量cursum来判断当前路径和,只需要用给定和target减去节点值,最终结束条件判断target==0即可
-
2、是否要回溯:二叉树的问题大部分是不需要回溯的,原因如下:
二叉树的递归部分:dfs(root->left),dfs(root->right)已经把可能的路径穷尽了,
因此到任意叶节点的路径只可能有一条,绝对不可能出现另外的路径也到这个满足条件的叶节点的;
而对比二维数组(例如迷宫问题)的DFS,for循环向四个方向查找每次只能朝向一个方向,并没有穷尽路径,
因此某一个满足条件的点可能是有多条路径到该点的
并且visited数组标记已经走过的路径是会受到另外路径是否访问的影响,这时候必须回溯 -
3、找到路径后是否要return: 取决于题目是否要求找到叶节点满足条件的路径,如果必须到叶节点,那么就要return;
但如果是到任意节点都可以,那么必不能return,因为这条路径下面还可能有更深的路径满足条件,还要在此基础上继续递归 -
4、是否要双重递归(即调用根节点的dfs函数后,继续调用根左右节点的pathsum函数):看题目要不要求从根节点开始的,还是从任意节点开始
二、非自顶而下:
这类题目一般解题思路如下:
设计一个辅助函数maxpath,调用自身求出以一个节点为根节点的左侧最长路径left和右侧最长路径right,那么经过该节点的最长路径就是left+right
接着只需要从根节点开始dfs,不断比较更新全局变量即可
int res=0;
int maxPath(TreeNode *root) //以root为路径起始点的最长路径
{
if (!root)
return 0;
int left=maxPath(root->left);
int right=maxPath(root->right);
res = max(res, left + right + root->val); //更新全局变量
return max(left, right); //返回左右路径较长者
}
这类题型DFS注意点:
1、left,right代表的含义要根据题目所求设置,比如最长路径、最大路径和等等
2、全局变量res的初值设置是0还是INT_MIN要看题目节点是否存在负值,如果存在就用INT_MIN,否则就是0
3、注意两点之间路径为1,因此一个点是不能构成路径的
递归函数返回值问题
递归函数什么时候要有返回值,什么时候没有返回值?特别是有的时候递归函数返回类型为bool类型。
- 如果需要搜索 整棵二叉树 且 不 用处理递归返回值,递归函数就不要返回值 。
(113. 路径总和 II) - 如果需要搜索整棵二叉树 且需要 处理递归返回值 ,递归函数就需要返回值。
(236. 二叉树的最近公共祖先)
() - 如果要搜索其中一条符合条件的路径 ,那么递归一定需要 返回值 ,因为遇到符合条件的路径了就要及时返回。
(112. 路径总和)
5.1 257. 二叉树的所有路径
迭代法:
很好理解
class Solution {
public:
vector<string> binaryTreePaths(TreeNode* root) {
// 迭代法
vector<string> result; // 保存最终路径集合
//用栈
stack<TreeNode*> treeSt;// 保存树的遍历节点
stack<string> pathSt; // 保存遍历路径的节点
if (root == NULL) return result;
treeSt.push(root);
pathSt.push(to_string(root->val));
while (!treeSt.empty()) {
TreeNode* node = treeSt.top(); treeSt.pop(); // 取出节点 中
string path = pathSt.top(); pathSt.pop(); // 取出该节点对应的路径
if (node->left == NULL && node->right == NULL) { // 遇到叶子节点
result.push_back(path);
}
if (node->right) { // 右
treeSt.push(node->right);
pathSt.push(path + "->" + to_string(node->right->val));
}
if (node->left) { // 左
treeSt.push(node->left);
pathSt.push(path + "->" + to_string(node->left->val));
}
}
return result;
}
};
递归法(理解回溯)
注意点:
- 递归终止条件
- 回溯
- 前序遍历
class Solution {
private:
void traversal(TreeNode* cur, vector<int>& path, vector<string>& result) {
path.push_back(cur->val); // 中,中为什么写在这里,因为最后一个节点也要加入到path中
// 这才到了叶子节点
if (cur->left == NULL && cur->right == NULL) {
string sPath;
for (int i = 0; i < path.size() - 1; i++) {
sPath += to_string(path[i]);
sPath += "->";
}
sPath += to_string(path[path.size() - 1]);
result.push_back(sPath);
return;
}
// 如果中 写在这里,即,判断终止条件之后的话,
//那么叶子节点 会因为符合了 终止条件而 直接return 跳过了
if (cur->left) { // 左
traversal(cur->left, path, result);
path.pop_back(); // 回溯
}
if (cur->right) { // 右
traversal(cur->right, path, result);
path.pop_back(); // 回溯
}
}
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
vector<int> path;
if (root == NULL) return result;
traversal(root, path, result);
return result;
}
};
模板1:
class Solution {
public:
vector<string> res;
vector<string> binaryTreePaths(TreeNode* root)
{
dfs(root, "");
return res;
}
void dfs(TreeNode*root, string path)
{
if (!root)
return;
path += to_string(root->val);
if (!root->left && !root->right)
{
res.push_back(path);
return;
}
dfs(root->left, path+"->");
dfs(root->right, path+"->");
}
};
5.2 404. 左叶子之和
注意点:
- 左叶子,不是二叉树左侧节点,所以不要上来想着层序遍历
- 左叶子的明确定义:节点A的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么A节点的左孩子为左叶子节点
- 注意,目标是左叶子值,所以,终止条件中的return 0, 都是指的是左叶子值是0;
递归三部曲:
-
确定递归函数的参数和返回值
判断一个树的左叶子节点之和,那么一定要传入树的根节点,递归函数的返回值为数值之和,所以为int -
确定终止条件
如果遍历到空节点,那么左叶子值一定是0
注意,只有当前遍历的节点是父节点,才能判断其子节点是不是左叶子。 所以如果当前遍历的节点是叶子节点,那其左叶子也必定是0,那么终止条件为:if (root == NULL) return 0; if (root->left == NULL && root->right== NULL) return 0; //其实这个也可以不写,如果不写不影响结果,但就会让递归多进行了一层。
-
确定单层递归的逻辑!!!
当遇到左叶子节点的时候,记录数值,
然后通过递归求取左子树左叶子之和,和 右子树左叶子之和,
相加便是整个树的左叶子之和。int leftValue = sumOfLeftLeaves(root->left); // 左 // 单层逻辑 // 左子树就是一个左叶子的情况 if (root->left && !root->left->left && !root->left->right) { leftValue = root->left->val; } int rightValue = sumOfLeftLeaves(root->right); // 右 int sum = leftValue + rightValue; // 中 return sum;
完整代码
int sumOfLeftLeaves(TreeNode* root) {
// 递归
// 终止条件
if(root ==NULL) return 0;
if(root ->left == NULL && root->right == NULL) return 0;
// 单层逻辑
// 左分支
int leftValue = sumOfLeftLeaves(root->left); // 左
if ( root->left && !root->left->left && !root->left->right) { // 左子树就是一个左叶子的情况
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 += node->left->val;
}
if (node->right) st.push(node->right);
if (node->left) st.push(node->left);
}
return result;
}
};
5.3 513. 找树左下角的值
递归(理解回溯)
递归三部曲
- 1.确定递归函数的参数和返回值
参数必须有要遍历的树的根节点,还有就是一个int型的变量用来记录最长深度。
本题还需要类里的两个全局变量,maxDepth用来记录最大深度,result记录最大深度最左节点的数值。 - 2.确定终止条件
当遇到叶子节点的时候,就需要统计一下最大的深度了,需要遇到叶子节点来更新最大深度 - 3.确定单层递归的逻辑
traversal 更新一个maxDepth, result 取 maxDepth的值
class Solution {
public:
int maxDepth = INT_MIN; // maxDepth用来记录最大深度
int result; // 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->left) { // 左
// traversal(root->left, depth+1 );
//}
if (root->right) { // 右
depth++;
traversal(root->right, depth);
depth--; // 回溯
}
// 精简回溯 // 右
//if (root->right) { // 右
// traversal(root->right, depth+1 );
//}
return ;
}
int findBottomLeftValue(TreeNode* root) {
traversal(root, 0);
// 注意: trversal函数,没有输出,就是一个处理函数,处理了result;
return result;
}
};
迭代法(模板)
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
queue<TreeNode*> que;
if (root != NULL) 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;
}
};
5.4 112. 路径总和
递归三部曲:
-
1.确定递归函数的参数和返回类型
参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。bool traversal(treenode* cur, int count) // 注意函数的返回类型
-
2.确定终止条件
首先计数器如何统计这一条路径的和呢?
不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。
如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。
如果遍历到了叶子节点,count不为0,就是没找到。if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0 if (!cur->left && !cur->right) return false; // 遇到叶子节点而没有找到合适的边,直接返回
-
3.确定单层递归的逻辑
因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。
递归函数是有返回值的,如果递归函数返回true,说明找到了合适的路径,应该立刻返回。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;
整体代码:
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 sum) {
if (root == NULL) return false;
return traversal(root, sum - root->val);
}
};
迭代法:pair结构
如果使用栈模拟递归的话,那么如果做回溯呢?
此时栈里一个元素不仅要记录该节点指针,还要记录从头结点到该节点的路径数值总和。
c++就我们用pair结构来存放这个栈里的元素。
定义为:pair<TreeNode*, int> pair<节点指针,路径数值>
class solution {
public:
bool haspathsum(TreeNode* root, int sum) {
if (root == null) return false;
// 此时栈里要放的是pair<节点指针,路径数值>
stack< pair<TreeNode*, int> > st;
st.push(pair<TreeNode*, int>(root, root->val));
while (!st.empty()) {
pair<TreeNode*, int> node = st.top();
st.pop();
// 如果该节点是叶子节点了,同时该节点的路径数值等于sum,那么就返回true
if (!node.first->left && !node.first->right && sum == node.second) {
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;
}
};
5.5 113. 路径总和 II
class Solution {
public:
// 113.路径总和ii要遍历整个树,找到所有路径,所以递归函数不要返回值!
vector<vector<int>> result;
vector<int> path;
void traversal(TreeNode* cur, int count) {
// 终止条件
if ( !cur->left && !cur->right && count == 0 ) {
// 遇到了叶子节点且找到了和为sum的路径
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 ;
}
vector<vector<int>> pathSum(TreeNode* root, int sum) {
result.clear();
path.clear();
if (root == NULL) return result;
path.push_back(root->val); // 把根节点放进路径
traversal(root, sum - root->val);
return result;
}
};
模板2:(内存消耗较大)
class Solution {
public:
vector<vector<int>> res;
vector<vector<int>> pathSum(TreeNode *root, int targetSum)
{
vector<int> path;
dfs(root, targetSum, path);
return res;
}
void dfs(TreeNode*root, int sum, vector<int> path)
{
if (!root)
return;
sum -= root->val;
path.push_back(root->val);
if (!root->left && !root->right && sum == 0)
{
res.push_back(path);
return;
}
dfs(root->left, sum, path);
dfs(root->right, sum, path);
}
};