二叉树:leetcode 110.平衡二叉树、257.二叉树的所有路径、404.左叶子之和

leetcode 110.平衡二叉树

leetcode 257.二叉树的所有路径

leetcode 404.左叶子之和

leetcode 110.平衡二叉树

回顾一下二叉树高度和深度的概念:

  • 二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。

  • 二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。

递归法

求高度,我们用的是后序遍历,求子节点的高度并层层向上返回。而求深度,我们用的是前序遍历,层层向下遍历统计节点的深度。

递归三部曲:

  1. 确定递归函数的参数和返回值

参数:当前传入节点。返回值:以当前传入节点为根节点的树的高度。

那么如何标记左右子树是否差值大于1呢?

如果当前传入节点为根节点的二叉树已经不是二叉平衡树了,还返回高度的话就没有意义了。

所以如果已经不是二叉平衡树了,可以返回-1 来标记已经不符合平衡树的规则了。

// -1 表示已经不是平衡二叉树了,否则返回值是以该节点为根节点树的高度
int getHeight(TreeNode* node)
  1. 确定终止条件

递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的树高度为0

if(node == NULL) return 0;
  1. 确定单层递归的逻辑

判断当前节点的左子树和右子树高度的差值与1的关系,如果差值小于等于1,则返回当前二叉树的高度。如果差值大于1,则返回-1,表示不为平衡二叉树。

int leftHeight = getHeight(node->left);
if(leftHeight == -1) return -1;
int rightHeight = getHeight(node->right);
if(rightHeight == -1) return -1;

int result;
if(abs(leftHeight - rightHeight) > 1) result = -1;
else result = 1 + max(leftHeight, rightHeight);
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 getHeight(TreeNode* node){
        if(node == NULL) return 0;
        int leftHeight = getHeight(node->left);
        if(leftHeight == -1) return -1;
        int rightHeight = getHeight(node->right);
        if(rightHeight == -1) return -1;
        int result;
        if(abs(leftHeight - rightHeight) > 1)
            result = -1;
        else
            result = 1 + max(leftHeight, rightHeight);
        return result;
    }
    bool isBalanced(TreeNode* root) {
        return getHeight(root) == -1 ? false : true;
    }
};

迭代法

可以使用层序遍历来求深度,但是就不能直接用层序遍历来求高度了,这就体现出求高度和求深度的不同。

本题的迭代方式可以先定义一个函数,专门用来求高度。

这个函数通过栈模拟的后序遍历(统一遍历方式)找每一个节点的高度(其实是通过求传入节点为根节点的最大深度来求的高度)。代码如下:

int getDepth(TreeNode* cur){
        stack<TreeNode*> st;
        if(cur != NULL) st.push(cur);
        int depth = 0, 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;
    }

总体代码:

/**
 * 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 getDepth(TreeNode* cur){
        stack<TreeNode*> st;
        if(cur != NULL) st.push(cur);
        int depth = 0, 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;
    }
};

此题用迭代法,其实效率很低,因为没有很好的模拟回溯的过程,所以迭代法有很多重复的计算。

虽然理论上所有的递归都可以用迭代来实现,但是有的场景难度可能比较大。

例如:都知道回溯法其实就是递归,但是很少人用迭代的方式去实现回溯算法!

因为对于回溯算法已经是非常复杂的递归了,如果再用迭代的话,就是自己给自己找麻烦,效率也并不一定高。

leetcode 257.二叉树的所有路径

这道题目要求从根节点到叶子的路径,所以需要前序遍历,这样才方便让父节点指向孩子节点,找到对应的路径。在这道题目中将涉及到回溯,因为我们要把路径记录下来,需要回溯来回退一个路径再进入另一个路径。

前序遍历以及回溯的过程如图:

要记住:递归和回溯是一家

递归法

递归三部曲:

  1. 确定递归函数的参数和返回值

传入根节点,传入一个用于记录单条路径的path数组和存放结果集的result数组,题目要求返回结果是一个string类型的数组,故result的类型是vector<string>。

void traversal(TreeNode* cur, vector<int>& path, vector<string>& result)
  1. 确定终止条件

本题的终止条件是找到了叶子节点就相当于找到了一条路径的终点,即某节点的左右孩子均为空。

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;
}

这里使用vector 结构path来记录路径,所以要把vector 结构的path转为string格式,再把这个string 放进 result里。

那么为什么使用了vector 结构来记录路径呢? 因为在下面处理单层递归逻辑的时候,要做回溯,使用vector方便来做回溯。

  1. 确定单层递归的逻辑

我们使用的是前序遍历(中左右),其中的“中”就是处理过程,本题中就是将节点添加到vector<int> path中,每遍历一个节点,就要把节点添加进来。

path.push_back(cur->val);    // 中
// 终止条件处理逻辑
// 左
// 右

这里有一个细节,我们需要把“中”的处理过程写到终止条件判断的前面。因为对于叶子节点来说,如果写到后面的话,在终止条件判断的if中就直接return了,那么叶子节点的值将不会被放到path中。

然后是递归和回溯的过程,上面的终止条件判断中没有判断cur是否为空,可能会出现对空指针进行操作的异常情况,那么在这里递归的时候,如果为空就不进行下一层递归了。

/****

所以递归前要加上判断语句,下面要递归的节点是否为空。递归完,还要做回溯,因为path不能一直加入节点,它还要删节点,然后才能加入新的节点。回溯和递归是一一对应的,有一个递归,就要有一个回溯,递归和回溯要写到同一个花括号里面才可以。

/****

代码如下:

path.push(cur->val);
// ...
if(cur->left){    // 花括号不能去,否则函数执行顺序会不对
    traversal(cur->left, path, result);
    path.pop_back();    // 回溯
}
if(cur->right){
    traversal(cur->right, path, result);
    path.pop_back();    // 回溯
}

总体代码如下:

/**
 * 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 {
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;

    }
};

如上代码精简了不少,也隐藏了不少东西。

/****

注意在函数定义的时候void traversal(TreeNode* cur, string path, vector<string>& result) ,定义的是string path,每次都是复制赋值,不用使用引用,否则就无法做到回溯的效果。(这里涉及到C++语法知识)

那么在如上代码中,貌似没有看到回溯的逻辑,其实不然,回溯就隐藏在traversal(cur->left, path + "->", result);中的 path + "->"。 每次函数调用完,path依然是没有加上"->" 的,这就是回溯了。

/****

如果写成这样:

if (cur->left) {
    path += "->";
    traversal(cur->left, path, result); // 左
}
if (cur->right) {
    path += "->";
    traversal(cur->right, path, result); // 右
}

此时就没有回溯了,这个代码就是通过不了的了。

如果想把回溯加上,就要 在上面代码的基础上,加上回溯,就可以AC了。

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(); // 回溯 '-'
        }
    }

如果把 path + "->"作为函数参数就是可以的,因为并没有改变path的数值,执行完递归函数之后,path依然是之前的数值(相当于回溯了)。

迭代法

非递归的方式,我们可以依然可以使用前序遍历的迭代方式来模拟遍历路径的过程,这里除了模拟递归需要一个栈,同时还需要一个栈来存放对应的遍历路径。使得保存路径的栈跟随模拟递归的栈同步进行操作,要注意在更新路径时要带上之前已经存储的路径。

/**
 * 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<string> binaryTreePaths(TreeNode* root) {
        stack<TreeNode*> st;    // 保持遍历节点的栈 TreeNode
        stack<string> pa;       // 保持路径的栈 string
        vector<string> result;
        if(root == NULL) return result;
        st.push(root);
        pa.push(to_string(root->val));
        while(!st.empty()){
            TreeNode* node = st.top();
            st.pop();
            string path = pa.top();
            pa.pop();
            if(node->right){
                st.push(node->right);
                pa.push(path + "->" + to_string(node->right->val));
            }
            if(node->left){
                st.push(node->left);
                pa.push(path + "->" + to_string(node->left->val));
            }
            if(node->left == NULL && node->right == NULL)
                result.push_back(path);
        }
        return result;
    }
};

leetcode 404.左叶子之和

给出左叶子的明确定义:节点A的左孩子不为空,且左孩子的左右孩子都为空(说明是叶子节点),那么A节点的左孩子为左叶子节点。

我们从左叶子的定义中可以知道,无法根据当前节点的信息及其孩子信息来判断该节点是不是左叶子,一定要通过其父节点才知道。所以我们不能直接遍历到叶子节点进行判断,而是要在其上一层进行判断,其判断逻辑如下:

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

递归法

递归的遍历顺序为后序遍历(左右中),是因为要通过递归函数的返回值来累加求取左叶子数值之和,层层向上返回给根节点,最终得到整一棵二叉树的左叶子之和。

递归三部曲

  1. 确定递归函数的参数和返回值

判断一个树的左叶子节点之和,那么一定要传入树的根节点,递归函数的返回值为数值之和,所以为int

使用题目中给出的函数就可以了。

int sumOfLeftLeaves(TreeNode* root)
  1. 确定终止条件

如果遇到空节点,那么左叶子之和一定为0;如果遇到叶子节点,那么其左叶子之和也为0。

if(root == NULL) return 0;
if(root->left == NULL && root->right == NULL) return 0;
// 注意这里的root不是指的根节点,而是遍历到的每一个节点,因为这里使用的是主函数进行遍历。
  1. 确定单层递归的逻辑

当遇到左叶子节点的时候,记录数值,然后通过递归求取左子树左叶子之和,和 右子树左叶子之和,相加便是整个树的左叶子之和。

int leftValue = sumOfLeftLeaves(root->left);    // 左
// 若当前节点的左孩子就是左叶子,此种情况特别考虑,若进入递归中,则会返回0而不是左叶子的值。
if (root->left && !root->left->left && !root->left->right) {
    leftValue = root->left->val;
}
int rightValue = sumOfLeftLeaves(root->right);  // 右

int sum = leftValue + rightValue;               // 中
return sum;

整体代码如下:

/**
 * 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 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 != NULL && root->left->left == NULL && root->left->right == NULL){
            leftValue = root->left->val;
        }
        int rightValue = sumOfLeftLeaves(root->right);    // 右
        return leftValue + rightValue;        // 中
    }
};

迭代法

本题迭代法使用前中后序都是可以的,只要把左叶子节点统计出来,就可以了。

可以写出一个前序遍历的迭代法,与上题迭代法类似。判断条件都是一样的,代码如下:

/**
 * 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 sumOfLeftLeaves(TreeNode* root) {
        stack<TreeNode*> st;
        int result = 0;
        if(root == NULL) return 0;
        st.push(root);
        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;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值