代码随想录算法训练营
—day16
文章目录
前言
今天是算法营的第16天,希望自己能够坚持下来!
今日任务:
● 513.找树左下角的值
● 112. 路径总和
● 106.从中序与后序遍历序列构造二叉树
● 105.从前序与中序遍历序列构造二叉树
一、513.找树左下角的值
递归法
在树的最后一行找到最左边的值。
找最后一行?找最大深度的那一层。用一个depth记录深度。
如何找最左边的呢?使用前序法,中左右,确保深度最大时,优先遍历左节点,保存左节点的值。
- 递归函数的参数和返回值:参数必须有要遍历的树的根节点,还有就是一个int型的变量用来记录最长深度。
- 终止条件:遇到叶子节点了,统计最大深度,记录当前节点值。
- 单层递归的逻辑:在找最大深度的时候,递归的过程中要使用回溯。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int result;
int maxDepth;
void traversal (TreeNode* node, int depth) {
//当遍历到叶子节点的时候终止
if (node->left == nullptr && node->right == nullptr) {
if (depth > maxDepth) {
maxDepth = depth;
result = node->val;
return;
}
}
//因为是要找最左边的节点,所以只要确保先遍历左边就可以了
if (node->left) traversal(node->left, depth + 1); //这里将回溯隐藏在了参数里
if (node->right) traversal(node->right, depth + 1);
return;
}
int findBottomLeftValue(TreeNode* root) {
result = 0;
maxDepth = INT_MIN; //要求最大值,先定义成最小值
if (root == nullptr) return result;
traversal(root, 1);
return result;
}
};
层序遍历法
记录每层的最左边的节点值即可。
代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
queue<TreeNode*> que;
int result = 0;
if (root == nullptr) return result;
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;
}
};
二、112. 路径总和
递归法
用目标值减去节点值来判断,最后target=0 说明路径总和等于目标值
有一条路径满足条件就可以立即返回true
这到底用什么遍历方式都可以,因为只是计算节点之和,没有顺序要求
代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
//用目标值减去节点值来判断,路径总和是否等于目标值
bool traversal(TreeNode* node, int target) {
if (node->left == nullptr && node->right == nullptr && target == 0) return true;
if (node->left == nullptr && node->right == nullptr) return false;
//有一条路径满足条件就可以立即返回true
if (node->left) {
if (traversal(node->left, target - node->left->val)) return true;
}
if (node->right) {
if (traversal(node->right, target - node->right->val)) return true;
}
return false;
}
bool hasPathSum(TreeNode* root, int targetSum) {
if (root == nullptr) return false;
//因为我们在递归里面没有处理根节点的值,所以先减了再放进去
return traversal(root, targetSum - root->val);
}
};
递归法精简版
每次减去当前节点的值,最后进入叶子节点时,目标值等于叶子节点值则为true
代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
//精简版递归
bool hasPathSum(TreeNode* root, int targetSum) {
if (!root) return false; //空节点判断移到开头
if (!root->left && !root->right && targetSum == root->val) return true;
//每次减去当前节点的值,最后进入叶子节点时,目标值等于叶子节点值则为true
return hasPathSum(root->left, targetSum - root->val) || hasPathSum(root->right, targetSum - root->val);
}
};
迭代法
用迭代法模拟递归,思路是一样的,但是需要同时保存节点和目标值。
在c++中可以用pair来保存两个参数。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
bool hasPathSum(TreeNode* root, int targetSum) {
//用pair同时存放节点和目标值
stack<pair<TreeNode*, int>> st;
if (root == nullptr) return false;
st.push(pair<TreeNode*, int> (root, targetSum));
while (!st.empty()) {
TreeNode* node = st.top().first;
int target = st.top().second;
st.pop();
// 如果该节点是叶子节点了,同时该节点的路径数值等于target,那么就返回true
if (!node->left && !node->right && target == node->val) return true;
if (node->left) st.push(pair<TreeNode*, int> (node->left, target - node->val)); //左
if (node->right) st.push(pair<TreeNode*, int> (node->right, target - node->val)); //右
}
return false;
}
};
相关题目:113. 路径总和 II
思路是一样的,只是变成要记录路径。使用前序遍历,多用一个数组来存储遍历过的节点,并且注意回溯,还有根节点的处理。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
vector<vector<int>> result;
//用目标值减去节点值来判断,路径总和是否等于目标值,path记录本次遍历的路径,满足条件就添加到结果集
void traversal(TreeNode* node, vector<int> path, int targe) {
if (node->left == nullptr && node->right == nullptr && targe == 0) {
result.push_back(path);
return;
}
if (node->left == nullptr && node->right == nullptr) {
return;
}
if (node->left) {
path.push_back(node->left->val);
traversal(node->left, path, targe - node->left->val);
path.pop_back(); //回溯
}
if (node->right) {
path.push_back(node->right->val);
traversal(node->right, path, targe - node->right->val);
path.pop_back(); //回溯
}
return;
}
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
if (root == nullptr) return result;
vector<int> path;
//递归里面没有处理root节点
path.push_back(root->val);
traversal(root, path, targetSum - root->val);
return result;
}
};
精简版:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
//递归,精简版
vector<vector<int>> result;
vector<int> path;
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
if (root == nullptr) return result;
path.push_back(root->val);//中
if (root->left == nullptr && root->right == nullptr && targetSum == root->val) {
result.push_back(path); //因为这里需要把当前节点也添加进去所以把中提前了
}
pathSum(root->left, targetSum - root->val); //左,每次进去之后都会push一次,pop一次
pathSum(root->right, targetSum - root->val); //右
path.pop_back(); //回溯,整个函数值调用了一次push,所以这里调用一次pop
return result;
}
};
三、106.从中序与后序遍历序列构造二叉树
思路:
- 后序遍历的最后一个元素,就是根节点;
- 用根节点可以将中序遍历分割成左中序和右中序;
- 左中序的大小=左后序的大小,以此分割将后序遍历数组分割成左后序和右后序;
- 左节点和右节点分别迭代左中序,左后序和右中序,右后序得出。
递归法
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
//如果后序数组大小为0,直接返回空节点
//取后序数组最后一个元素,是根节点,用元素值创建一个二叉树节点
//如果后序数组大小为1,那么说明是叶子节点了,直接返回根节点
//在中序数组中找到根节点,获取下标
//根据上面的下标就可以切割后序数组:左后序数组,右后序数组
//后序数组舍弃末尾元素
//左后序数组的大小=左中序数组的大小,以大小来切割中序数组
//递归传入左中序,左后序和右中序,右后序
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if (inorder.size() == 0 || postorder.size() == 0) return nullptr;
//后序数组最后一个元素,就是当前的中间节点
int rootValue = postorder[postorder.size()-1];
TreeNode* root = new TreeNode(rootValue);
//叶子节点
if (postorder.size() == 1) return root;
//找到中序遍历的切割垫
int rootIndex;
for (rootIndex = 0; rootIndex < inorder.size(); rootIndex++) {
if (inorder[rootIndex] == rootValue) break;
}
//用根节点切割中序数组 [0, rootIndex)
vector<int> leftInorder (inorder.begin(), inorder.begin()+rootIndex);
//[rootIndex+1, end) 不要中间节点
vector<int> rightInorder (inorder.begin()+rootIndex+1, inorder.end());
// 后序数组舍弃末尾元素
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 = buildTree(leftInorder, leftPostorder);
root->right = buildTree(rightInorder, rightPostorder);
return root;
}
};
迭代法(高效版)
其实不需要再迭代中重复创建新数组,只获取新的数组下标就可以了。
代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
//如果后序数组大小为0,直接返回空节点
//取后序数组最后一个元素,是根节点,用元素值创建一个二叉树节点
//如果后序数组大小为1,那么说明是叶子节点了,直接返回根节点
//在中序数组中找到根节点,获取下标
//根据上面的下标就可以切割后序数组:左后序数组,右后序数组
//后序数组舍弃末尾元素
//左后序数组的大小=左中序数组的大小,以大小来切割中序数组
//递归传入左中序,左后序和右中序,右后序
//[inorderBegin,inorderEnd) [postorderBegin,postorderEnd)
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 rootIndex;
//在[inorderBegin,inorderEnd)内遍历
for (rootIndex = inorderBegin; rootIndex < inorderEnd; rootIndex++) {
if (inorder[rootIndex] == rootValue) break;
}
// 切割中序数组
// 左中序区间,左闭右开[leftInorderBegin, leftInorderEnd)
int leftInorderBegin = inorderBegin;
int leftInorderEnd = rootIndex;
// 右中序区间,左闭右开[rightInorderBegin, rightInorderEnd)
int rightInorderBegin = rootIndex + 1;
int rightInorderEnd = inorderEnd;
// 切割后序数组
// 左后序区间,左闭右开[leftPostorderBegin, leftPostorderEnd)
int leftPostorderBegin = postorderBegin;
int leftPostorderEnd = postorderBegin + leftInorderEnd - leftInorderBegin; //终止位置是 需要加上 中序区间的大小size
// 右后序区间,左闭右开[rightPostorderBegin, rightPostorderEnd)
int rightPostorderBegin = leftPostorderEnd;
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 nullptr;
int rootValue = postorder[postorder.size() - 1];
TreeNode* root = new TreeNode(rootValue);
// 左闭右开的原则
return traversal(inorder, 0, inorder.size(), postorder, 0, postorder.size());
}
};
相关题目:105.从前序与中序遍历序列构造二叉树
思路是一样的,只是变成前序的第一个元素是根节点。
代码如下:
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode() : val(0), left(nullptr), right(nullptr) {}
* TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
* TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
* };
*/
class Solution {
public:
TreeNode* traversal(vector<int>& preorder, int preorderBegin, int preorderEnd, vector<int>& inorder, int inorderBegin, int inorderEnd) {
if (preorderBegin == preorderEnd) return nullptr;
int rootValue = preorder[preorderBegin]; //这里要用preorderBegin
TreeNode* root = new TreeNode(rootValue);
//子节点
if (preorderEnd - preorderEnd == 1) return root;
int rootIndex;
//从inorderBegin遍历到inorderEnd
for (rootIndex = inorderBegin; rootIndex < inorderEnd; rootIndex++) {
if (inorder[rootIndex] == rootValue) break;
}
//分割中序数组
//左中序数组
int leftInorderBegin = inorderBegin;
int leftInorderEnd = rootIndex;
//右中序数组
int rightInorderBegin = rootIndex + 1;
int rightInorderEnd = inorderEnd;
//分割前序数组
//左前序数组
int leftPreorderBegin = preorderBegin + 1;
int lefttPreorderEnd = leftPreorderBegin + (leftInorderEnd - leftInorderBegin);
//右前序数组
int rightPreorderBegin = lefttPreorderEnd;
int rightPreorderEnd = preorderEnd;
root->left = traversal(preorder, leftPreorderBegin, lefttPreorderEnd, inorder, leftInorderBegin, leftInorderEnd);
root->right = traversal(preorder, rightPreorderBegin, rightPreorderEnd, inorder, rightInorderBegin, rightInorderEnd);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if (preorder.size() == 0 || inorder.size() == 0) return nullptr;
return traversal(preorder, 0, preorder.size(), inorder, 0, inorder.size());
}
};
};
总结
今天主要是学习了:
1.对二叉树的题目又有了更深入的理解,前两道基本看完题目也有个大概思路,看完视频之后也可以自己写出来代码了。
2.第三道题细节很多,需要结合debug来想清楚里面的细节。
明天继续加油!