目录
Leetcode 513.找树左下角的值
题目链接:Leetcode 513.找树左下角的值
题目描述:给定一个二叉树,在树的最后一行找到最左边的值。
思路:首先要注意题目寻找的是最后一行的最左边节点的值,这个节点不一定是左孩子,也可能是右孩子。这道题既可以用递归法也可以用迭代法。递归寻找最大深度或者一层一层迭代寻找最大深度。当然这两种方法都是基于二叉树的遍历代码实现的,不了解的可以先看看这两个:深度优先遍历 层序遍历
代码如下:(递归法)
class Solution {
public:
int maxDepth = INT_MIN;
int result;
void traverssal(TreeNode* root, int depth) {
if (root->left == nullptr &&
root->right == nullptr) { //找到叶子节点就判断是否需要更新
if (depth > maxDepth) {
maxDepth = depth;
result = root->val;
}
return;
}
if (root->left) { //先向左找
traverssal(root->left, depth + 1);
}
if (root->right) { //左边没有就去右面找
traverssal(root->right, depth + 1);
}
return;
}
int findBottomLeftValue(TreeNode* root) {
traverssal(root, 0);
return result;
}
};
(迭代法)
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
queue<TreeNode*> que;
int result = 0;
if (root != nullptr)
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;
}
};
Leetcode 112. 路径总和
题目链接:Leetcode 112. 路径总和
题目描述:给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。
思路:通过遍历二叉树,判断路径之和是否符合题意。这里有一个小技巧,每访问过一个节点就用目标值减去该节点的值,最后结果为0的就是目标路径。
代码如下:(递归法)
class Solution {
public:
bool traversal(TreeNode* cur, int count) {
//找到目标路径直接返回true
if (cur->left == nullptr && cur->right == nullptr && count == 0)
return true;
//找到叶子节点但不是目标路径,返回false
if (cur->left == nullptr && cur->right == nullptr)
return false;
//如果左子树找到了,则向上返回true
if (cur->left && traversal(cur->left, count - cur->left->val))
return true;
//如果右子树找到了,则向上返回true
if (cur->right && traversal(cur->right, count - cur->right->val))
return true;
//说明左右子树都没找到,返回false
return false;
}
bool hasPathSum(TreeNode* root, int targetSum) {
if (root == nullptr)
return false; //空树一定不存在
return traversal(root, targetSum - root->val);
}
};
(迭代法)
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if (root == nullptr)
return false;
//栈要存放节点和路径数据
stack<pair<TreeNode*, int>> st;
st.push(pair<TreeNode*, int>(root, root->val));
while (!st.empty()) {
pair<TreeNode*, int> node = st.top();
st.pop();
// 如果该节点是叶子节点了,同时该节点的路径数值等于targetSum,那么就返回true
if (!node.first->left && !node.first->right &&
targetSum == 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;
}
};
Leetcode 113. 路径总和ii
题目链接:Leetcode 113. 路径总和ii
题目描述:给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
说明: 叶子节点是指没有子节点的节点。
思路:这道题和上道题类似,上道题只需要判断是否有目标路径,这道题需要将所有合适的路径返回。本题用栈模拟代码书写比较繁琐,但是逻辑和上道题的类似:都是用栈模拟递归遍历二叉树。
代码如下:(递归法)
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void traversal(TreeNode* cur, int count) {
if (!cur->left && !cur->right &&
count == 0) { //找到叶子节点并且路径之和等于targetSum
result.push_back(path);
return;
}
if (!cur->left && !cur->right)
return;
if (cur->left) {
path.push_back(cur->left->val);
traversal(cur->left, count - cur->left->val); //向下访问
path.pop_back(); //回溯(访问后将path恢复原状)
}
if (cur->right) {
path.push_back(cur->right->val);
traversal(cur->right, count - cur->right->val); //向下访问
path.pop_back(); //回溯(访问后将path恢复原状)
}
return;
}
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
//result.clear();
//path.clear();
if (root == nullptr)
return result;
path.push_back(root->val);
traversal(root, targetSum - root->val);
return result;
}
};
Leetcode 106.从中序与后序遍历序列构造二叉树
题目链接:Leetcode 106.从中序与后序遍历序列构造二叉树
题目描述:根据一棵树的中序遍历与后序遍历构造二叉树。
注意: 你可以假设树中没有重复的元素。
在本题开始之前,大家可以试着询问自己这样一件事:给定一颗二叉树,能否熟练的写出该二叉树的前、中、后序遍历的节点顺序。举个例子,下面这颗二叉树的前、中、后序遍历能否熟练说出来呢?
前序遍历:6427153
中序遍历:2741653
后序遍历:7214356
如果能够熟练的写出这三种序列,理解这道题与下道题的思路会更加容易。
思路:我们发现前序遍历的第一个数和后序遍历的最后一个数是当前二叉树的根节点。再观察中序遍历,我们发现根节点6左侧的数字是左子树的节点,6右侧的数字是右子树的节点。这种性质的原理来源于前、中、后序的遍历顺序。基于上述思路,我们可以尝试将思路转化为代码的逻辑:
-
第一步:如果数组大小为零的话,说明是空节点了。
-
第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。
-
第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点。
-
第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)切完之后别忘了删掉后序数组的最后一个数字。
-
第五步:切割后序数组,切成后序左数组和后序右数组。
-
第六步:递归处理左区间和右区间。
尽管有了上述思路,敲上代码之后发现切割的边界问题也是一个易错点:一定要保证切割范围的一致性。说人话就是切割的区间要么是左闭右闭区间,要么是左闭右开区间(一般只有这两种写法)
代码如下:(每次递归通过新建数组体现出分割)
class Solution {
public:
TreeNode* traversal(vector<int>& inorder, vector<int>& postorder) {
if (postorder.size() == 0)
return nullptr;
//后序遍历数组最后一个元素就是当前的中间节点
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;
}
//切割中序数组
vector<int> leftInorder(inorder.begin(), inorder.begin() + index);
vector<int> rightInorder(inorder.begin() + index + 1, inorder.end());
// postorder舍弃末尾元素
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;
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if (inorder.size() == 0 || postorder.size() == 0)
return nullptr;
return traversal(inorder, postorder);
}
};
当然这种写法只是方便理解“切割数组”这一思想,我们也可以通过切割数组下标来实现:
代码如下:(每次递归利用下标分割)
class Solution {
public:
TreeNode* traversal(vector<int>& inorder, int inorderBegin, int inorderEnd,
vector<int>& postorder, int postorderBegin,
int postorderEnd) {
if (postorderBegin == postorderEnd)
return nullptr;
//保存后序数组最后一个节点
int rootValue = postorder[postorderEnd - 1];
TreeNode* root = new TreeNode(rootValue);
//说明后序遍历数组为空
if (postorderEnd - postorderBegin == 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 leftPostorderBegin = postorderBegin;
// 终止位置是 需要加上 中序区间的大小size
int leftPostorderEnd = postorderBegin + index - inorderBegin;
//右区间
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;
}
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());
}
};
Leetcode 105.从前序与中序遍历序列构造二叉树
题目链接:Leetcode 105.从前序与中序遍历序列构造二叉树
题目描述:根据一棵树的前序遍历与中序遍历构造二叉树。
注意: 你可以假设树中没有重复的元素。
思路:和上面那道题类似,只是切割数据从最后一个节点数据变成了第一个节点数据。
代码如下:(每次递归利用下标分割)
class Solution {
public:
TreeNode* traversal(vector<int>& inorder, int inorderBegin, int inorderEnd,
vector<int>& preorder, int preorderBegin,
int preorderEnd) {
if (preorderBegin == preorderEnd)
return nullptr;
int rootValue = preorder[preorderBegin]; // 注意用preorderBegin 不要用0
TreeNode* root = new TreeNode(rootValue);
if (preorderEnd - preorderBegin == 1)
return root;
int index;
for (index = inorderBegin; index < inorderEnd; index++) {
if (inorder[index] == rootValue)
break;
}
// 切割中序数组
// 中序左区间,左闭右开[leftInorderBegin, leftInorderEnd)
int leftInorderBegin = inorderBegin;
int leftInorderEnd = index;
// 中序右区间,左闭右开[rightInorderBegin, rightInorderEnd)
int rightInorderBegin = index + 1;
int rightInorderEnd = inorderEnd;
// 切割前序数组
// 前序左区间,左闭右开[leftPreorderBegin, leftPreorderEnd)
int leftPreorderBegin = preorderBegin + 1;
int leftPreorderEnd =
preorderBegin + 1 + index -
inorderBegin; // 终止位置是起始位置加上中序左区间的大小size
// 前序右区间, 左闭右开[rightPreorderBegin, rightPreorderEnd)
int rightPreorderBegin = preorderBegin + 1 + (index - inorderBegin);
int rightPreorderEnd = preorderEnd;
root->left = traversal(inorder, leftInorderBegin, leftInorderEnd,
preorder, leftPreorderBegin, leftPreorderEnd);
root->right = traversal(inorder, rightInorderBegin, rightInorderEnd,
preorder, rightPreorderBegin, rightPreorderEnd);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if (inorder.size() == 0 || preorder.size() == 0)
return NULL;
// 参数坚持左闭右开的原则
return traversal(inorder, 0, inorder.size(), preorder, 0,
preorder.size());
}
};
总结:最近的题目越来越综合性,但凡二叉树的基础知识,递归、迭代思想的代码实现,以及二叉树的基本遍历方式等内容不太熟悉,理解题解思路都会吃力,不过慢慢熟悉这些内容之后再看这些题目,有种“轻舟已过万重山”的感觉!
最后,如果文章有错误,请在评论区或私信指出,让我们共同进步!