代码随想录算法训练营第十八天|513.找树左下角的值、112. 路径总和、113. 路径总和ii、106.从中序与后序遍历序列构造二叉树、105.从前序与中序遍历序列构造二叉树

LeetCode 513 找树左下角的值

题目链接:https://leetcode.cn/problems/find-bottom-left-tree-value/

思路:

用到了深度这一二叉树属性。该题用任意一种遍历方式均可。因为本题不需要对中节点进行处理。

迭代法的关键在于终止条件。要理解题意,什么叫最底层的最左边。

代码:

  • 递归法

/**
 * 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 Maxdepth = INT_MIN;
    int result = 0;
    
    int findBottomLeftValue(TreeNode* root) {
        int depth = 0;
        traversal(root,depth);
        return result;
    }

    void traversal(TreeNode *node,int depth)
    {
        // 终止条件
        if(!node->left&&!node->right)
        {
            if(depth>Maxdepth)
            {
                Maxdepth = depth;
                result = node->val;
            }
        }

        // 左
        if(node->left)
        {
            // 记录深度的增大
            depth++;
            traversal(node->left,depth);
            depth--;    // 回溯的过程,根节点走完左边之后,可以回过头来走右边
        }

        // 右
        if(node->right)
        {
            depth++;
            traversal(node->right,depth);
            depth--;
        }
    }

};
  • 层序遍历

/**
 * 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;
        if(root!=nullptr)
            que.push(root);
        int result = 0;
        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;
        

    }
};

总结

想到层序遍历法的思路,但是没写出来。迭代法的话没想到思路,看了视频后可以理解题意,希望下次自己可以写出来。

路径总和

注意事项:

递归函数什么时候需要返回值?什么时候不需要返回值?这里总结如下三点:

  • 如果需要搜索整棵二叉树且不用处理递归返回值,递归函数就不要返回值。(这种情况就是本文下半部分介绍的113.路径总和ii)

  • 如果要搜索其中一条符合条件的路径,那么递归一定需要返回值,因为遇到符合条件的路径了就要及时返回。(112.路径总和)

相关题目

LeetCode 112 路径总和

题目链接:https://leetcode.cn/problems/path-sum/

思路:

自己的思路:采用累加器,将路径上的节点和加起来,直到叶节点处,然后在叶节点处进行判断是否等于题目要求的和。注意:需要用回溯。

卡哥的思路:采用累减器,用题目要求的总和减去节点上的值,然后在叶节点处判断此时,和是否为0,若为0代表该路径符合题意。注意:需要回溯

代码:
  • 自己的代码

/**
 * 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) {
        int sum = 0;
        if(root==nullptr)
            return false;
        
        return traversal(root,sum,targetSum);
    }

    bool traversal(TreeNode *root,int sum,int targetSum)
    {
        sum += root->val;

        if(!root->left&&!root->right)
        {
            if(sum==targetSum)
                return true;
            else
                return false;
        }
        

        if(root->left)
        {
            // 此时递归里面的sum是复制赋值,不影响父节点的sum,所以其实已经回溯了
            if(traversal(root->left,sum,targetSum))
                return true;
            // sum -= root->left->val;
        } 

        if(root->right)
        {
            if(traversal(root->right,sum,targetSum))
                return true;
            // sum -= root->right->val;
        }

        return false;
    }
};
  • 卡哥的代码

/**
 * 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 == nullptr) return false;
        return traversal(root,targetSum-root->val);

    }

    bool traversal(TreeNode *node,int count)
    {
        // 终止条件
        if(!node->left&&!node->right&&count == 0)   return true;
        if(!node->left&&!node->right)   return false;

        //左
        if(node->left)
        {
            // 此处需要回溯是因为在将count放进去递归函数之前,先对count减掉了节点的值
            count -= node->left->val;
            if(traversal(node->left,count))
                return true;
            count += node->left->val;
        }

        // 右
        if(node->right)
        {
            count -= node->right->val;
            if(traversal(node->right,count))
                return true;
            count += node->right->val;
        }

        return false;
        
    }
};
总结

第一时间想到了累加器的方法,也想到了要回溯,因为昨天做了求所有路径的题目。但是在回溯的过程中,还是没想明白要不要写出来。因为我的是复制赋值,所以它本身就带有回溯了。还是要多从减法方面思考,从而减少代码量。

LeetCode 113 路径总和ii

题目链接:https://leetcode.cn/problems/path-sum-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>> pathSum(TreeNode* root, int targetSum) {
        vector<vector<int>>result;
        vector<int>path;
        if(!root)   return result;
        traversal(root,path,result,targetSum-root->val);
        return result;
    }

    void traversal(TreeNode *node,vector<int>&path,vector<vector<int>>&result,int count)
    {
        path.push_back(node->val);

        if(!node->left&&!node->right&&count==0)
        {
            result.push_back(path);
            return ;
        }
        if(!node->left&&!node->right)
            return;
        
        if(node->left)
        {
            count -= node->left->val;
            traversal(node->left,path,result,count);
            count += node->left->val;   // 回溯
            path.pop_back();    // 回溯
        }

        if(node->right)
        {
            count -= node->right->val;
            traversal(node->right,path,result,count);
            count += node->right->val;  // 回溯
            path.pop_back();    //回溯
        }
    }

};
总结

很开心自己可以通过之前学到的东西,自己把题目AC了。但是还是有小瑕疵,就是在回溯path的时候,要将path设为引用的,即函数的参数path是引用的,不然不需要特意加一行path.pop_back()的代码。

用前序和中序/中序和后序去构造二叉树

整体思想:

后序:左右中 中序:左中右 前序:中左右

以后序和中序构造二叉树为例:

理论基础:

将后序数组的最后一个元素为切割点,先切中序数组,根据中序数组,反过来再切后序数组。一层一层切下去,每次后序数组最后一个元素就是节点元素。注:前序的话就反过来,以第一个元素为切割点。

代码步骤:

  • 第一步:如果数组大小为零的话,说明是空节点了。

  • 第二步:如果不为空,那么取后序数组最后一个元素作为节点元素。

  • 第三步:找到后序数组最后一个元素在中序数组的位置,作为切割点

  • 第四步:切割中序数组,切成中序左数组和中序右数组 (顺序别搞反了,一定是先切中序数组)

  • 第五步:切割后序数组,切成后序左数组和后序右数组

  • 第六步:递归处理左区间和右区间

注意细节:

  • 在切割中序和后序数组的时候,要保证循环不变量,即以左闭右闭还是左闭右开去左切割。切割中序和后序数组的循环不变量都需要保证一样。

  • 中序数组大小一定是和后序数组的大小相同的。所以可以通过中序的左区间大小去切割后序数组。

相关题目

LeetCode 106 从中序与后序遍历序列构造二叉树

题目链接:https://leetcode.cn/problems/construct-binary-tree-from-inorder-and-postorder-traversal/

代码:
  • 思路清晰版

/**
 * 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* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if(inorder.empty()||postorder.empty())  return nullptr;
        return traversal(inorder,postorder);
    }

    TreeNode* traversal(vector<int> &inorder,vector<int> &postorder)
    {
        // 第一步
        // 确定是否为空节点
        if(postorder.size()==0) return nullptr;

        // 第二步
        // 确定节点元素
        int rootval = postorder[postorder.size()-1];
        TreeNode *root = new TreeNode(rootval);

        // 处理叶子节点
        if(postorder.size()==1) return root;

        // 第三步
        // 找到切割点
        int index = 0;
        for(index;index<inorder.size();index++)
        {
            if(inorder[index]==rootval)
                break;
        }

        // 第四步
        // 左闭右开原则
        // 切割中序数组
        vector<int>leftinorder = vector<int>(inorder.begin(),inorder.begin()+index);
        vector<int>rightinorder = vector<int>(inorder.begin()+index+1,inorder.end());   // +1的作用是为了去掉节点元素

        // 第五步
        // 左闭右开原则
        // 切割后序数组
        postorder.resize(postorder.size()-1);   // 先将节点元素排除掉
        vector<int>leftpostorder = vector<int>(postorder.begin(),postorder.begin()+leftinorder.size());
        vector<int>rightpostorder = vector<int>(postorder.begin()+leftinorder.size(),postorder.end());

        // 第六步
        // 递归处理左孩子和右孩子
        root->left = traversal(leftinorder,leftpostorder);
        root->right = traversal(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:
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if(inorder.empty()||postorder.empty())  return nullptr;
        return traversal(inorder,0,inorder.size(),postorder,0,postorder.size());
    }

    TreeNode* traversal(vector<int> &inorder,int inorderBegin,int inorderEnd,vector<int> &postorder,int postorderBegin,int postorderEnd)
    {
        // 第一步
        // 确定是否为空节点
        if(postorderBegin == postorderEnd) return nullptr;

        // 第二步
        // 确定节点元素
        int rootval = postorder[postorderEnd-1];
        TreeNode *root = new TreeNode(rootval);

        // 处理叶子节点
        if(postorderEnd-postorderBegin==1) return root;

        // 第三步
        // 找到切割点
        int index = inorderBegin;
        for(index;index<inorderEnd;index++)
        {
            if(inorder[index]==rootval)
                break;
        }

        // 第四步
        // 左闭右开原则
        // 切割中序数组
        int leftinorderBegin = inorderBegin;
        int leftinorderEnd = index;
        int rightinorderBegin = index+1;
        int rightinorderEnd = inorderEnd;

        // 第五步
        // 左闭右开原则
        // 切割后序数组
        int leftpostorderBegin = postorderBegin;
        int leftpostorderEnd = postorderBegin+index-inorderBegin;
        int rightpostorderBegin = postorderBegin+(index-inorderBegin);
        int rightpostorderEnd = postorderEnd-1; // 要排除最后一个元素


        // 第六步
        // 递归处理左孩子和右孩子
        root->left = traversal(inorder,leftinorderBegin,leftinorderEnd,postorder,leftpostorderBegin,leftpostorderEnd);
        root->right = traversal(inorder,rightinorderBegin,rightinorderEnd,postorder,rightpostorderBegin,rightpostorderEnd);

        return root;
    }

};

LeetCode 105 从前序与中序遍历序列构造二叉树

题目链接:https://leetcode.cn/problems/construct-binary-tree-from-preorder-and-inorder-traversal/

代码:
  • 思路清晰版

/**
 * 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* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if(preorder.empty()||inorder.empty())   return nullptr;
        return traversal(preorder,inorder);
    }

    TreeNode* traversal(vector<int>&preorder,vector<int>&inorder)
    {
        // 第一步
        // 处理空节点
        if(preorder.size()==0)  return nullptr;

        // 第二步
        // 寻找节点元素
        int rootval = preorder[0];
        TreeNode *root = new TreeNode(rootval);

        // 处理叶子节点
        if(preorder.size()==1)  return root;

        // 第三步
        // 找切割点
        int index = 0;
        for(index;index<inorder.size();index++)
        {
            if(inorder[index]==rootval)
                break;
        }

        // 第四步
        // 切割中序数组
        vector<int>leftinorder(inorder.begin(),inorder.begin()+index);
        vector<int>rightinorder(inorder.begin()+index+1,inorder.end());

        // 第五步
        // 切割前序数组
        vector<int>leftpreorder(preorder.begin()+1,preorder.begin()+index+1);
        vector<int>rightpreorder(preorder.begin()+index+1,preorder.end());

        // 第六步
        // 处理左子树和右子树的递归
        root->left = traversal(leftpreorder,leftinorder);
        root->right = traversal(rightpreorder,rightinorder);

        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:
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if(preorder.empty()||inorder.empty())   return nullptr;
        return traversal(preorder,0,preorder.size(),inorder,0,inorder.size());

    }

    TreeNode* traversal(vector<int> &preorder,int preorderBegin,int preorderEnd,vector<int> &inorder,int inorderBegin,int inorderEnd)
    {
        // 第一步
        // 处理空节点
        if(preorderBegin==preorderEnd)  return nullptr;

        // 第二步
        // 确定节点元素
        int rootval = preorder[preorderBegin];
        TreeNode *root = new TreeNode(rootval);

        // 第三步
        // 确定中序数组切割点
        int index = inorderBegin;
        for(index;index<inorderEnd;index++)
        {
            if(inorder[index]==rootval)
                break;
        }

        // 第四步
        // 切割中序数组
        int leftinorderBegin = inorderBegin;
        int leftinorderEnd = index; // 此处直接是index的原因:因为在上面是从inorderBegin开始找index的值的
        int rightinorderBegin = leftinorderEnd+1;
        int rightinorderEnd = inorderEnd;

        // 第五步
        // 切割前序数组
        int leftpreorderBegin = preorderBegin+1;
        int leftpreorderEnd = preorderBegin+1+index-inorderBegin;
        int rightpreorderBegin = leftpreorderEnd;
        int rightpreorderEnd = preorderEnd;

        // // 以下为日志
        // cout << "----------" << endl;
        // cout << "leftInorder :";
        // for (int i = leftinorderBegin; i < leftinorderEnd; i++) {
        //     cout << inorder[i] << " ";
        // }
        // cout << endl;

        // cout << "rightinorder :";
        // for (int i = rightinorderBegin; i < rightinorderEnd; i++) {
        //     cout << inorder[i] << " ";
        // }
        // cout << endl;

        // cout << "leftpreorder :";
        // for (int i = leftpreorderBegin; i < leftpreorderEnd; i++) {
        //     cout << preorder[i] << " ";
        // }
        // cout << endl;

        // cout << "rightpreorder :";
        // for (int i = rightpreorderBegin; i < rightpreorderEnd; i++) {
        //     cout << preorder[i] << " ";
        // }
        // cout << endl;
        // cout<<"leftinorderBegin = "<<leftinorderBegin<<endl;
        // cout<<"leftinorderEnd = "<<leftinorderEnd<<endl;
        // cout<<"rightinorderBegin = "<<rightinorderBegin<<endl;
        // cout<<"rightinorderEnd = "<<rightinorderEnd<<endl;

        // cout<<"leftpreorderBegin = "<<leftpreorderBegin<<endl;
        // cout<<"leftpreorderEnd = "<<leftpreorderEnd<<endl;
        // cout<<"rightpreorderBegin = "<<rightpreorderBegin<<endl;
        // cout<<"rightpreorderEnd = "<<rightpreorderEnd<<endl;


        // 第六步
        // 左子树和右子树的递归处理
        root->left = traversal(preorder,leftpreorderBegin,leftpreorderEnd,inorder,leftinorderBegin,leftinorderEnd);
        root->right = traversal(preorder,rightpreorderBegin,rightpreorderEnd,inorder,rightinorderBegin,rightinorderEnd);


        return root;
    }
};

总结

一开始看视频学习了如何从后序与中序遍历序列构造二叉树。写完整版代码的时候可以写下来,但是在写优化版代码的时候,对于切割中序数组和后序数组那里总是会出现溢出的问题。后面在写从前序与中序遍历序列构造二叉树中,也是完整代码可以写出,优化版的代码还是会有溢出问题。需要多加练习。

今日总结:

今天的题目加强了递归和回溯的练习,开始从二叉树的属性过渡到构造二叉树。自己今天也写出了回溯的代码,很开心。说明自己还是有一直在进步的。继续加油!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值