代码随想录训练营第十七天——平衡二叉树,二叉树的所有路径,左叶子之和

leetcode 110. 平衡二叉树

题目链接:平衡二叉树

递归法——后序遍历:

本题需要比较高度,所以要采用后序遍历。
递归法计算当前传入节点的高度,注意当传入的当前节点为根节点的的二叉树已经不是二叉平衡树时,返回高度就没有意义了,所以可以通过返回-1标记已经不是平衡二叉树了。

class Solution {
public:
    int getheight(TreeNode* node)//返回该节点为根节点的二叉树的高度,如果不是平衡二叉树则返回-1
    {
        if(node == NULL) return 0;
        int left_height = getheight(node->left);
        if(left_height==-1) return -1;
        int right_height = getheight(node->right);
        if(right_height==-1) return -1;
        return abs(left_height - right_height) > 1 ? -1 : 1 + max(left_height,right_height);
    }
    bool isBalanced(TreeNode* root) {
       return getheight(root) == -1 ? false : true; //高度-1不是平衡二叉树,高度不是-1是平衡二叉树
    }
};

迭代法:

层序遍历可以用来求深度,但是不能直接用来求高度。本题使用迭代法比较复杂。
本题需要专门定义一个函数用来求高度。同时使用栈的后序遍历求每一个节点的高度(求传入节点为根节点的最大深度来求高度)
求高度函数:

// cur节点的最大深度,就是cur的高度
int getDepth(TreeNode* cur) {
    stack<TreeNode*> st;
    if (cur != NULL) st.push(cur);
    int depth = 0; // 记录深度
    int result = 0;
    while (!st.empty()) {
        TreeNode* node = st.top();
        if (node != NULL) {
            st.pop();
            st.push(node);                          // 中
            st.push(NULL);
            depth++;
            if (node->right) st.push(node->right);  // 右
            if (node->left) st.push(node->left);    // 左

        } else {
            st.pop();
            node = st.top();
            st.pop();
            depth--;
        }
        result = result > depth ? result : depth;  //循环中不断判断求解当前深度是不是更大
    }
    return result;
}

然后使用栈通过后序遍历,每遍历到一个节点,判断左右孩子的高度是否符合。
判断是否是平衡二叉树代码:

bool isBalanced(TreeNode* root) {
    stack<TreeNode*> st;
    if (root == NULL) return true;
    st.push(root);
    while (!st.empty()) {
        TreeNode* node = st.top();                       // 中
        st.pop();
        if (abs(getDepth(node->left) - getDepth(node->right)) > 1) { // 判断左右孩子高度是否符合
            return false;
        }
        if (node->right) st.push(node->right);           // 右(空节点不入栈)
        if (node->left) st.push(node->left);             // 左(空节点不入栈)
    }
    return true;
}

整体代码如下:

class Solution {
private:
    int getDepth(TreeNode* cur) {
        stack<TreeNode*> st;
        if (cur != NULL) st.push(cur);
        int depth = 0; // 记录深度
        int result = 0;
        while (!st.empty()) {
            TreeNode* node = st.top();
            if (node != NULL) {
                st.pop();
                st.push(node);                          // 中
                st.push(NULL);
                depth++;
                if (node->right) st.push(node->right);  // 右
                if (node->left) st.push(node->left);    // 左
            } else {
                st.pop();
                node = st.top();
                st.pop();
                depth--;  
            }
            result = result > depth ? result : depth;
        }
        return result;
    }

public:
    bool isBalanced(TreeNode* root) {
        stack<TreeNode*> st;
        if (root == NULL) return true;
        st.push(root);
        while (!st.empty()) {
            TreeNode* node = st.top();                       // 中
            st.pop();
            if (abs(getDepth(node->left) - getDepth(node->right)) > 1) {
                return false;
            }
            if (node->right) st.push(node->right);           // 右(空节点不入栈)
            if (node->left) st.push(node->left);             // 左(空节点不入栈)
        }
        return true;
    }
};

leetcode 257. 二叉树的所有路径

题目链接:二叉树的所有路径

  1. 本题需要求解从根节点到叶子节点的路径,所以需要用到前序遍历,方便从父节点指向子节点,找到对应的路径。
  2. 本题需要使用到回溯,因为需要把路径记录下来,需要回溯法来回退一个路径再进入另一个路径。回溯和递归是一家的。

递归法:

按照递归三部曲:
1.递归函数参数及返回值

void traversal(TreeNode* cur, vector<int>& path, vector<string>& result)

2.递归终止条件
递归终止条件是要找到叶子节点:当前节点cur不为空,且左右孩子都为空
vector结构记录路径便于回溯。

if (cur->left == NULL && cur->right == NULL) { // 遇到叶子节点(不需要判断cur不为空,因为通过其他逻辑控制空节点不入栈)
    string sPath;  //记录路径
    for (int i = 0; i < path.size() - 1; i++) { // 将path里记录的路径转为string格式
        sPath += to_string(path[i]);
        sPath += "->";
    }
    sPath += to_string(path[path.size() - 1]); // 记录最后一个节点(叶子节点)
    result.push_back(sPath); // 收集一个路径
    return;
}

3.单层递归逻辑
回溯和递归是一一对应的,有一个递归,就要有一个回溯,所以回溯和递归的写法要放在一起。

if (cur->left)  //控制空节点不入栈
 {
    traversal(cur->left, path, result);  //递归
    path.pop_back(); // 回溯(回溯和递归要同时写在花括号中)
}
if (cur->right) 
{
    traversal(cur->right, path, result);//递归
    path.pop_back(); // 回溯(回溯和递归要同时写在花括号中)
}

整体代码如下(版本一):

class Solution {
private:
    void traversal(TreeNode* cur, vector<int>& path, vector<string>& result) {
        path.push_back(cur->val); // 中,中为什么写在这里,因为叶子节点也要加入到path中,所以放在叶子节点判断之前 
        if (cur->left == NULL && cur->right == NULL) // 这才到了叶子节点
         {
            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 (cur->left) { // 左 
            traversal(cur->left, path, result);
            path.pop_back(); // 回溯
        }
        if (cur->right) { // 右
            traversal(cur->right, path, result);
            path.pop_back(); // 回溯
        }
    }
public:
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> result;
        vector<int> path;
        if (root == NULL) return result;
        traversal(root, path, result);
        return result;
    }
};

上述代码可以精简如下(版本一):

class Solution {
private:

    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); // 右
    }
public:
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> result;
        string path;
        if (root == NULL) return result;
        traversal(root, path, result);
        return result;
    }
};

精简代码中的函数定义中使用的是string path,每次都是复制赋值,不用使用引用,因为每次path的值不能发生改变,否则就无法做到回溯的效果(C++语法)。
上述代码中,回溯隐藏在traversal(cur->left, path + “->”, result);中的 path + “->”。 每次函数调用完,path依然是原值,并没有加上"->" ,path的值没有改变,这就是回溯了。
如果把path + "->"写在函数外面,就必须在外面加上回溯。
整体代码如下(版本二):

void traversal(TreeNode* cur, string path, vector<string>& result) {
        path += to_string(cur->val); // 中,中为什么写在这里,因为最后一个节点也要加入到path中
        if (cur->left == NULL && cur->right == NULL) {
            result.push_back(path);
            return;
        }
        if (cur->left) {
            path += "->";
            traversal(cur->left, path, result); // 左
            path.pop_back(); // 回溯 '>'
            path.pop_back(); // 回溯 '-'
        }
        if (cur->right) {
            path += "->";
            traversal(cur->right, path, result); // 右
            path.pop_back(); // 回溯'>'
            path.pop_back(); // 回溯 '-'
        }
    }
public:
    vector<string> binaryTreePaths(TreeNode* root) {
        vector<string> result;
        string path;
        if (root == NULL) return result;
        traversal(root, path, result);
        return result;
    }
};

比较以上两种版本的写法:

  1. 如果把 path + "->"作为函数的参数就没有pop_back操作了,因为并没有改变path的数值,执行完递归函数之后,path依然是之前的数值(相当于回溯了)
  2. 第二版本算法只回溯了 -> 部分,path += to_string(cur->val)并没有回溯。因为函数参数使用的是string path,并没有加上引用&,本层递归中,path + 该节点数值,该层递归结束,上一层path的数值并没有发生改变。参数中不带引用,不作地址拷贝,只做内容拷贝。

迭代法:

迭代法依然使用前序遍历

class Solution {
public:
    vector<string> binaryTreePaths(TreeNode* root) {
        stack<TreeNode*> treeSt;// 保存树的遍历节点
        stack<string> pathSt;   // 保存遍历路径的节点
        vector<string> result;  // 保存最终路径集合
        if (root == NULL) return result;
        treeSt.push(root);
        pathSt.push(to_string(root->val));
        while (!treeSt.empty()) {
            TreeNode* node = treeSt.top(); treeSt.pop(); // 取出节点 中
            string path = pathSt.top();pathSt.pop();    // 取出该节点对应的路径
            if (node->left == NULL && node->right == NULL) { // 遇到叶子节点
                result.push_back(path);
            }
            if (node->right) { // 右
                treeSt.push(node->right);
                pathSt.push(path + "->" + to_string(node->right->val));
            }
            if (node->left) { // 左
                treeSt.push(node->left);
                pathSt.push(path + "->" + to_string(node->left->val));
            }
        }
        return result;
    }
};

leetcode 404. 左叶子之和

题目链接:左叶子之和
左叶子节点:节点A的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么A节点的左孩子为左叶子节点
判断当前节点是不是左叶子是无法判断的,必须要通过节点的父节点来判断其左孩子是不是左叶子
如果该节点的左节点不为空,该节点的左节点的左节点为空,该节点的左节点的右节点为空,则找到了一个左叶子,判断代码如下:

if (node->left != NULL && node->left->left == NULL && node->left->right == NULL)
{    
}

递归法——后序遍历:
递归法的遍历顺序是后序遍历,因为需要通过递归函数的返回值累加求取左叶子之和。

class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        if (root == NULL) return 0;
        if (root->left == NULL && root->right== NULL) return 0;
        int leftValue = sumOfLeftLeaves(root->left);    // 左
        if (root->left && !root->left->left && !root->left->right) { // 左子树就是一个左叶子的情况
            leftValue = root->left->val;
        }
        int rightValue = sumOfLeftLeaves(root->right);  // 右
        int sum = leftValue + rightValue;               // 中
        return sum;
    }
};

迭代法——前序遍历:
迭代法使用前中后序都是可以的,只要把左叶子节点统计出来。
前序遍历

class Solution {
public:
    int sumOfLeftLeaves(TreeNode* root) {
        stack<TreeNode*> st;
        if (root == NULL) return 0;
        st.push(root);
        int result = 0;
        while (!st.empty()) {
            TreeNode* node = st.top();
            st.pop();
            if (node->left != NULL && node->left->left == NULL && node->left->right == NULL) {
                result += node->left->val; //中,处理逻辑
            }
            if (node->right) st.push(node->right); //右
            if (node->left) st.push(node->left); //左
        }
        return result;
    }
};

你可能感兴趣的:(leetcode,算法,数据结构)