LeetCode: 513.找树左下角的值
力扣代码链接
文字讲解:LeetCode: 513.找树左下角的值
视频讲解:怎么找二叉树的左下角? 递归中又带回溯了,怎么办?
基本思路
对题目进行一下分析,要找二叉树最底层最左边节点的数值。首先要是最后一行,然后是最左边的值。如果使用递归法,如何判断是最后一行呢,其实就是深度最大的叶子节点一定是最后一行。
那么如何找最左边的呢?使用层序遍历当然是可以的,而如果使用递归的方法可以使用前序遍历(当然中序,后序都可以,因为本题没有中间节点的处理逻辑,只要左优先就行),保证优先左边搜索,然后记录深度最大的叶子节点,此时就是树的最后一行最左边的值。
注意:有一个地方容易误解,即二叉树最底层最左边的节点不一定是左孩子,也可能是右孩子!!!
- 确定递归函数的参数和返回值
首先需要遍历根节点,以及定义一个变量记录深度为int类型,返回值可以为void。本题还需要类里的两个全局变量,maxLen用来记录最大深度,result记录最大深度最左节点的数值。
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;
C++代码
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;}
};
LeetCode: 112. 路径总和、113.路径总和II
力扣代码链接:112. 路径总和、113.路径总和II
文字讲解:LeetCode: 112. 路径总和、113.路径总和II
视频讲解:拿不准的遍历顺序,搞不清的回溯过程,我太难了!
LeetCode: 112. 路径总和
这道题我们要遍历从根节点到叶子节点的路径看看总和是不是目标和。可以使用深度优先遍历的方式(本题前中后序都可以,无所谓,因为中节点也没有处理逻辑)来遍历二叉树。
如下图所示,这里明显要采用回溯算法,如果当前路径不符合,我们就从倒数第二天路还是回溯,还是不行就回溯倒数第三、倒数第四条路,直到找到合适的路径或者全部搜索完成。
- 确定递归函数的参数和返回值
参数:需要二叉树的根节点,还需要一个计数器,这个计数器用来计算二叉树的一条边之和是否正好是目标和,计数器为int型。
返回值:我们应该是只要找一条符合条件的路径,递归函数就需要及时返回,那么返回类型是bool。
bool traversal(treenode* cur, int count) // 注意函数的返回类型
- 确定终止条件
不要去累加然后判断是否等于目标和,那么代码比较麻烦,可以用递减,让计数器count初始为目标和,然后每次减去遍历路径节点上的数值。如果最后count == 0,同时到了叶子节点的话,说明找到了目标和。如果遍历到了叶子节点,count不为0,就是没找到。
if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0
if (!cur->left && !cur->right) return false; // 遇到叶子节点而没有找到合适的边,直接返回
- 确定单层递归的逻辑
因为终止条件是判断叶子节点,所以递归的过程中就不要让空节点进入递归了。递归函数是有返回值的,如果递归函数返回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;
C++代码
class Solution {
private:bool traversal(TreeNode* cur, int count) {if (!cur->left && !cur->right && count == 0) return true; // 遇到叶子节点,并且计数为0if (!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);}
};
LeetCode: 113.路径总和II
基本思路
113.路径总和ii要遍历整个树,找到所有路径,所以递归函数不要返回值!
- 确定递归函数的参数和返回值
参数:依旧需要遍历所有的根节点,并且使用count记录目标和。
返回值:返回值为void,因为我们要遍历整个树,然后记录路径,所以递归函数要不返回值
注意:本题要求我们求出整条路径,因此我们需要一个数组记录所有路径,另外还需要一个数组记录所有满足条件的路径。
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 ;
C++代码
class solution {
private: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 ;}public: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;}
};
LeetCode: 106. 从中序与后序遍历序列构造二叉树
力扣代码链接
文字讲解:LeetCode: 106. 从中序与后序遍历序列构造二叉树
视频讲解:坑很多!来看看你掉过几次坑
基本思路
首先回忆一下如何根据两个顺序构造一个唯一的二叉树,相信理论知识大家应该都清楚,就是以 后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。
把步骤量化出来:
(第一步:如果数组大小为零的话,说明是空节点了。)
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)
第五步:切割后序数组,切成后序左数组和后序右数组
第六步:递归处理左区间和右区间
可以根据步骤写出整体框架:
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);
C++代码
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;}// 切割中序数组// 左闭右开区间:[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);return root;}
public:TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {if (inorder.size() == 0 || postorder.size() == 0) return NULL;return traversal(inorder, postorder);}
};