一、前言
参考文献:代码随想录
今天是周六,还是二叉树,不过这次的题目又相对于前两天又有了一些提升。所以就慢慢来体会递归的细节吧!
二、找树左下角的值
1、思路:
这一题的第一思路想到的是=有两种,第一种递归法,第二种迭代法(利用队列来实现层序遍历),但是我尝试递归后,并不能很快的解答出来,因为我理解的是出自最左子树上的最左边的节点的数值(想法太单纯了)
得到了以下代码:
class Solution {
private:
int result;
void getLeftBotten(TreeNode* node) {
if (node == NULL) {
return;
}
getLeftBotten(node->left);
getLeftBotten(node->right);
if (node->left != NULL && node->left->left == NULL) {
result = node->left->val;
}
return;
}
public:
int findBottomLeftValue(TreeNode* root) {
if (root->left == NULL && root->right == NULL) {
return root->val;
}
getLeftBotten(root);
return result;
}
};
这个就是一个简单的后序遍历,然后在中序遍历上做点手脚就OK了。但是这样并不能解决题目的意思,因为我永远找的是最后一个出现的左叶子节点。
2、迭代法:
class Solution {
public:
int findBottomLeftValue(TreeNode* root) {
// 常规模板
// 利用队列来实现层序遍历
int result = 0;
queue<TreeNode*> que;
if (root == NULL) return 0;
que.push(root);
while (!que.empty()) {
int size = que.size();
for (int i = 0; i < size; i++) {
TreeNode* node = que.front();
que.pop();
// 遍历到每一层的第一位(最左边的一位)时,更新一下result
if (i == 0) {
result = node->val;
}
if (node->left) que.push(node->left);
if (node->right) que.push(node->right);
}
}
return result;
}
};
迭代法的原理比较简单,我这里就不做过多的说明了。
3、递归法:
这里的递归方法运用了回溯,所以比较抽象。
但是经过我在群里和大佬的讨论,已经对回溯这种抽象的概念有了一定的理解了,就是把一个值拿用完一次之后,又退回到原来的值去用一次,大概就是这样。
本题目的解法的递归思路如下:
(1)返回值为void,没有需要返回的,参数为一个深度和TreeNode的指针,为什么要有深度呢?因为这里的最底部光靠单纯徐遍历是无法确定的,必须要有一个深度参数来确定是否为最底层。
(2)终止条件为是否非叶子节点,为叶子节点就需要更新depth的值,更新返回值;
(3)单层递归逻辑比较简单,采用的是前序遍历(后序,中序都可以,只需要左子树先操作即可)。
递归代码如下:
class Solution {
int result;
int depthMax = INT_MIN;
void getLeftBotton(TreeNode* node, int depth) {
if (node->left == NULL && node->right == NULL) {
if (depth > depthMax) {
depthMax = depth;
result = node->val;
}
}
if (node->left) {
depth++;
getLeftBotton(node->left, depth);
depth--; // 回溯过程,给右子树用
}
if (node->right) {
depth++;
getLeftBotton(node->right, depth);
depth--;
}
return;
}
public:
int findBottomLeftValue(TreeNode* root) {
getLeftBotton(root, 0);
return result;
}
};
三、路经总和
1、思路:
这个题目还是使用递归,但是对于我来说比较麻烦,我没有啥思路,我写了一个错误的版本,但是没有通过,我就去问gpt教授了,gpt教授给了我一个方案,我总结如下:
(1)返回值,传入参数:
返回bool类型,传入的参数为TreeNode、sum(统计和)、tagetSum(比较参数)
(2)终止条件:
遍历到空节点了就返回false,sum == targetSum即返回true
(3)单层递归:
左右同时递归,然后并起来,如果有就true,就返回true。
2、代码:
class Solution {
private:
bool findSum(TreeNode* node, int sum, int targetSum) {
if (node == NULL) {
return false;
}
sum += node->val;
if (node->left == NULL && node->right == NULL) {
return sum == targetSum;
}
// 分开递归,其中有一个为真,就返回真!
return findSum(node->left, sum, targetSum) || findSum(node->right, sum, targetSum);
}
public:
bool hasPathSum(TreeNode* root, int targetSum) {
return findSum(root, 0, targetSum);
}
};
看了一眼卡哥写的,感觉也很精妙,整体思路如下:
(1)使用的sum初始化为targetSum,在每次递归的时候就是剪掉当前的值,如果到了根节点并且sum==0的话就说明存在路径,可以返回false
代码如下:
class Solution {
private:
bool findSum(TreeNode* node, int sum) {
if(node == NULL) {
return false;
} else if (node->left == NULL && node->right == NULL && sum == 0) {
return true;
}
if (node->left) {
sum -= node->left->val;
if (findSum(node->left, sum)) return true;
sum += node->left->val; // 回溯,hh
}
if (node->right) {
sum -= node->right->val;
if (findSum(node->right, sum)) return true;
sum += node->right->val;
}
return false;
}
public:
bool hasPathSum(TreeNode* root, int targetSum) {
if (root == NULL) return false;
return findSum(root, targetSum - root->val);
}
};
简化之后和gpt教授的内容差不多。
四、路径总和||:
1、思路:
思路差不多,但是多了一个返回所有路径的要求,错误代码如下,我的第一思考。。
class Solution {
private:
vector<vector<int>> result;
void getSums(TreeNode* node, vector<int>& vec, int sum) {
if (node == NULL) return;
if (sum == 0 && node->left == NULL && node->right == NULL) {
result.push_back(vec);
}
if (node->left) {
vector<int> leftVec = vec;
leftVec.push_back(node->val);
getSums(node->left, leftVec, sum - node->val);
}
if (node->right) {
vector<int> rightVec = vec;
rightVec.push_back(node->val);
getSums(node->right, rightVec, sum - node->val);
}
}
public:
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
vector<int> vec;
getSums(root, vec, targetSum);
return result;
}
};
真的是递归、回溯傻傻分不清楚。。
我去看了gpt教授的思路后,我有不禁感叹,我还是太菜了!
思路如下:
(1)返回值为void,传入参数有三个
void getSums(TreeNode* node, vector<int>& vec, int sum)
一个是节点,一个是记录路径上的值,最后一个是用来比较是否成功的。
(2)终止条件为
if (sum == node->val && node->left == NULL && node->right == NULL) {
result.push_back(vec);
}
这里有一个小细节,sum == node->val因为在这里时已经把当前节点的值存入到了vec当中,只需要判断最后的sum是否减去node->val等于0,即可保存当前路径了。
(3)单层递归如下
getSums(node->left, vec, sum - node->val);
getSums(node->right, vec, sum - node->val);
}
vec.pop_back();// 回溯
伴随着两个数据的回溯,确实是相当烧脑了。
2、整体代码如下:
class Solution {
private:
vector<vector<int>> result;
void getSums(TreeNode* node, vector<int>& vec, int sum) {
if (node == NULL) return;
vec.push_back(node->val);
if (sum == node->val && node->left == NULL && node->right == NULL) {
result.push_back(vec);
} else {
getSums(node->left, vec, sum - node->val);
getSums(node->right, vec, sum - node->val);
}
vec.pop_back();// 回溯
}
public:
vector<vector<int>> pathSum(TreeNode* root, int targetSum) {
vector<int> vec;
getSums(root, vec, targetSum);
return result;
}
};
五、从中序与后序遍历序列构造二叉树
1、思路:
确认过眼神,这是我不会的题。。虽然做过一次,但是已经没有任何印象了,这里的思路很难想出来。只有去看卡哥视频了。
这题卡个总结了如下六点:
(1)如果数组大小为零,那就是空节点了。
(2)如果不为空,那就取后续数组的最后一个元素为根节点。
(3)找到后续元素在中序数组中的位置,进行写个。
(4)优先切割中序数组
(5)切割后序数组
(6)递归左右区间
这样卡哥一总结下来就觉得很清晰了,我来一步一步实现。
2、代码如下:
class Solution {
private:
TreeNode* traversal(vector<int> &inorder, vector<int>& postorder) {
// 第一步
if (postorder.size() == 0) return NULL;
// 第二步
int postVal = postorder[postorder.size() - 1];
TreeNode* root = new TreeNode(postVal);
// 叶子节点
if (postorder.size() == 1) return root;
// 第三步
int delimiterIndex;
for (int i = 0; i < inorder.size(); i++) {
if (inorder[i] == postVal) {
delimiterIndex = i;
break;
}
}
// 第四步,切割中序数组
// 确定区间,采用左闭右开的区间[0, dilimiterIndex)
vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
// [delimiterIndex, end)
vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end());
// 第五步,切割后序数组
// 1、舍弃最后一个元素
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;
}
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
if (inorder.size() == 0 || postorder.size() == 0) return NULL;
return traversal(inorder, postorder);
}
};
六、中序遍历和前序遍历构成二叉树
1、思路:
这题和上题可以说一模一样,只需要修改一下细节即可
2、代码如下:
class Solution {
private:
TreeNode* traversal(vector<int> &inorder, vector<int>& preorder) {
// 第一步
if (preorder.size() == 0) return NULL;
// 第二步
int preVal = preorder[0];
TreeNode* root = new TreeNode(preVal);
// 叶子节点
if (preorder.size() == 1) return root;
// 第三步
int delimiterIndex;
for (int i = 0; i < inorder.size(); i++) {
if (inorder[i] == preVal) {
delimiterIndex = i;
break;
}
}
// 第四步,切割中序数组
// 确定区间,采用左闭右开的区间[0, dilimiterIndex)
vector<int> leftInorder(inorder.begin(), inorder.begin() + delimiterIndex);
// [delimiterIndex, end)
vector<int> rightInorder(inorder.begin() + delimiterIndex + 1, inorder.end());
// 第五步,切割后序数组
// 1、舍弃第一个元素
preorder.erase(preorder.begin());
// 这里就有一个数学小知识了,左中序数组的长度应该等于左后序数组的长度。所以这就是突破点。
// 这里我们继续使用左闭右开的区间
vector<int> leftPerorder(preorder.begin(), preorder.begin() + leftInorder.size());
vector<int> rightPerorder(preorder.begin() + leftInorder.size(), preorder.end());
// 第六步递归
root->left = traversal(leftInorder, leftPerorder);
root->right = traversal(rightInorder, rightPerorder);
return root;
}
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
if (preorder.size() == 0 || inorder.size() == 0) return 0;
return traversal(inorder, preorder);
}
};
今日学习时间:2.5个小时;
leave message:
Each man has own ambition in life, in spite of everything they were determined.
人各有志,终不为次移。