110. 平衡二叉树
心得
- 计算左右高度差然后求绝对值高度差用来判断是否平衡,判断逻辑写在了主函数,递归函数仅仅写获取高度,相当于只判断一次,忽略判断左右子树本身平衡二叉树的情况
- 应牢记递归思路,判断始终是迭代递归下去的,而不是单层,要想好单层逻辑
题解
- 对于深度,递归法,用前序遍历,只有从上往下,中左右才能逐步往下求深度
- 高度用后序遍历,只有左右中层层网上才能求高度,只有求得左右子树情况才能用来处理逻辑
- 对于迭代,层序遍历无法求得左右子树高度,用栈来模拟后序遍历,但是处逻辑需要用单独函数(用栈来计算左右子树的高度,通过最大深度即为高度)然后对高度进行逻辑判断,最后中右左入栈出栈即可
- 理论上递归都可以用迭代来求解,但是本题有点复杂,而且迭代存在重复计算,效率并不高,不推荐使用迭代
class Solution {
public:
int getHeight(TreeNode* node) {
if (node == nullptr) return 0;
int leftHeight = getHeight(node->left);
int rightHeight = getHeight(node->right);
if (leftHeight == -1) return -1;
if (rightHeight == -1) return -1;
return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
}
bool isBalanced(TreeNode* root) {
if (root == nullptr) return true;
return getHeight(root) == -1 ? false : true;
}
};
257. 二叉树的所有路径
心得
- 看到题目想的是深度优先,但是想的是中序,但是这样无法得到路径,每种应该都尝试过一下即能想到大致思路
- 返回结果看起来复杂,箭头连接,需要处理
题解
-
考虑递归遍历,由于路径从上往下,而且需要回溯,保存位置,只能采用前序,先保存中,然后下去左右,然后回溯遍历右侧
-
注意本次递归的三步走与之前写法有较大差异,
- 首先终止条件区分之前的高度深度,他们传入叶子结点的下一个结点即空节点,终止判断空即可,但是本题只要叶子结点不用往下遍历,所以终止条件判断是够为叶子结点
- 其次中序的处理过程放在首位,考虑的是终止条件处理需要加入的叶子结点,否则最后一个无法保存
- 最后需要回溯才能找到上次的位置,而且一个迭代对应一次回溯,不可递归2次回溯一次
-
精简版本隐藏了回溯细节,二刷来理解
- 核心在于形参类型值拷贝string path而不是引用的地址拷贝string & path
- 在每次path + “→”过程中,返回时,并不会导致原来path的改变,即完成了回溯
class Solution {
public:
void traversal(TreeNode* node, vector<int>& path, vector<string>& result) {
path.push_back(node->val); // 中, 写在这里因为下面的逻辑不加入叶子结点,结果需要最后的叶子结点所以提前保存
if (node->left == nullptr && node->right == nullptr) { // 终止条件,没有当前结点node==nullptr判断,而是叶子结点判断,由于
// 其逻辑逻辑是加入叶子结点,下面控制不入非空
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;
}
if (node->left) {
traversal(node->left, path, result); // 左
path.pop_back(); // 回溯
}
if (node->right) {
traversal(node->right, path, result); // 右
path.pop_back(); // 回溯
}
}
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
vector<int> path;
if (root == nullptr) return result;
traversal(root, path, result);
return result;
}
};
// 递归部分可精简
void traversal(TreeNode* cur, string path, vector<string>& result) {
path += to_string(cur->val); // 中
if (cur->left == NULL && cur->right == NULL) {
result.push_back(path);
return;
}
if (cur->left) traversal(cur->left, path + "->", result); // 左
if (cur->right) traversal(cur->right, path + "->", result); // 右
}
404. 左叶子之和
心得
- 理解题目左叶子的定义,首先是叶子结点,其次是左,想的是要到回溯父节点,然后判断,但是一想回溯就感觉很麻烦,就没深入考虑,其实思路类似的,就是用父节点来判断,但是不用回溯,直接遍历过程中的终止条件上移一层即可
题解
- 明确叶子结点定义,利用父结点来判断其是不是左叶子结点,区别之前二叉树每次通过孩子结点判断终止条件做法
- 递归采用前中后序都可以,后序便于理解,迭代法判断条件一致
// 递归——后序
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if (root == nullptr) return 0;
// 当前为叶子结点,必然其左叶子为0,其实不写也行,多一层递归
if (root->left == nullptr && root->right == nullptr) return 0;
int leftSum = sumOfLeftLeaves(root->left); // 左
// 这里位置其实在哪都可以,下面的leftsum只是借来的,如果在该变量前使用则新建个变量接收即可
if (root->left && !root->left->left && !root->left->right) {
leftSum = root->left->val;
}
int rightSum = sumOfLeftLeaves(root->right); // 右
return leftSum + rightSum; // 中
}
};
// 精简
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if (root == NULL) return 0;
int leftValue = 0;
if (root->left != NULL && root->left->left == NULL && root->left->right == NULL) {
leftValue = root->left->val;
}
return leftValue + sumOfLeftLeaves(root->left) + sumOfLeftLeaves(root->right);
}
};
// 迭代法,
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if (root == nullptr) return 0;
stack<TreeNode*> st;
if (root != nullptr) st.push(root);
int sum = 0;
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
if (node->left && !node->left->left && !node->left->right) {
sum += node->left->val;
}
if (node->left) st.push(node->left);
if (node->right) st.push(node->right);
}
return sum;
}
};