牛客Top101第十天

BM28.二叉树的最大深度

方法一:递归(推荐使用)

知识点:二叉树递归

递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。

而二叉树的递归,则是将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。

思路:

最大深度是所有叶子节点的深度的最大值,深度是指树的根节点到任一叶子节点路径上节点的数量,因此从根节点每次往下一层深度就会加1。因此二叉树的深度就等于根节点这个1层加上左子树和右子树深度的最大值,即depth​=max(leftdepth​,rightdepth​)+1。而每个子树我们都可以看成一个根节点,继续用上述方法求的深度,于是我们可以对这个问题划为子问题,利用递归来解决:

  • 终止条件: 当进入叶子节点后,再进入子节点,即为空,没有深度可言,返回0.
  • 返回值: 每一级按照上述公式,返回两边子树深度的最大值加上本级的深度,即加1.
  • 本级任务: 每一级的任务就是进入左右子树,求左右子树的深度。

具体做法:

  • step 1:对于每个节点,若是不为空才能累计一次深度,若是为空,返回深度为0.
  • step 2:递归分别计算左子树与右子树的深度。
  • step 3:当前深度为两个子树深度较大值再加1。
class Solution {
public:
    int maxDepth(TreeNode* root) {
        //空节点没有深度
        if(root == NULL) 
            return 0;
        //返回子树深度+1
        return max(maxDepth(root->left), maxDepth(root->right)) + 1; 
    }
};

 BM29.二叉树中和为某一值的路径(一)

方法一:递归(推荐使用)

知识点:二叉树递归

递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。

而二叉树的递归,则是将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。

思路:

既然是检查从根到叶子有没有一条等于目标值的路径,那肯定需要从根节点遍历到叶子,我们可以在根节点每次往下一层的时候,将sum减去节点值,最后检查是否完整等于0. 而遍历的方法我们可以选取二叉树常用的递归前序遍历,因为每次进入一个子节点,更新sum值以后,相当于对子树查找有没有等于新目标值的路径,因此这就是子问题,递归的三段式为:

  • 终止条件: 每当遇到节点为空,意味着过了叶子节点,返回。每当检查到某个节点没有子节点,它就是叶子节点,此时sum减去叶子节点值刚好为0,说明找到了路径。
  • 返回值: 将子问题中是否有符合新目标值的路径层层往上返回。
  • 本级任务: 每一级需要检查是否到了叶子节点,如果没有则递归地进入子节点,同时更新sum值减掉本层的节点值。

具体做法:

  • step 1:每次检查遍历到的节点是否为空节点,空节点就没有路径。
  • step 2:再检查遍历到是否为叶子节点,且当前sum值等于节点值,说明可以刚好找到。
  • step 3:检查左右子节点是否可以有完成路径的,如果任意一条路径可以都返回true,因此这里选用两个子节点递归的或。
class Solution {
public:
    bool hasPathSum(TreeNode* root, int sum) {
        //空节点找不到路径
        if(root == NULL) 
            return false;
        //叶子节点,且路径和为sum
        if(root->left == NULL && root->right == NULL && sum - root->val == 0) 
            return true;
        //递归进入子节点
        return hasPathSum(root->left, sum - root->val) || hasPathSum(root->right, sum - root->val);
    }
};

方法二:非递归(扩展思路)

知识点1:栈 栈是一种仅支持在表尾进行插入和删除操作的线性表,这一端被称为栈顶,另一端被称为栈底。元素入栈指的是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;元素出栈指的是从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

知识点2:深度优先搜索(dfs)

深度优先搜索一般用于树或者图的遍历,其他有分支的(如二维矩阵)也适用。它的原理是从初始点开始,一直沿着同一个分支遍历,直到该分支结束,然后回溯到上一级继续沿着一个分支走到底,如此往复,直到所有的节点都有被访问到。

思路:

在二叉树中能够用递归解决的问题,很多时候我们也可以用非递归来解决。这里遍历过程也可以使用栈辅助,进行dfs(深度优先搜索)遍历,检查往下的路径中是否有等于sum的路径和。

注意,这里仅是dfs,而不是前序遍历,左右节点的顺序没有关系,因为每次往下都是单独添加某个节点的值相加然后继续往下,因此左右节点谁先遍历不管用。

具体做法:

  • step 1:首先检查空节点,空树没有路径。
  • step 2:使用两个栈同步遍历,一个栈记录节点,辅助深度优先搜索,另一个栈跟随记录到该节点为止的路径和(C++中可以在一个栈中嵌套pair实现)。根节点及根节点值先进栈。
  • step 3:遍历的时候每次弹出两个栈中的内容,判断是否是叶子节点且路径和是否等于目标值。
  • step 4:没有到叶子节点就将左右子节点(如果有)加入栈中,并跟随加入路径和。
  • step 5:如果遍历结束也没有找到路径和,则该二叉树中没有。
class Solution {
public:
    bool hasPathSum(TreeNode* root, int sum) {
        //空节点找不到路径
        if(root == NULL) 
            return false;
        //栈辅助深度优先遍历,并记录到相应节点的路径和
        stack<pair<TreeNode*, int> > s; 
        //根节点入栈
        s.push({root, root->val}); 
        while(!s.empty()){
            auto temp = s.top();
            s.pop();
            //叶子节点且路径和等于sum
            if(temp.first->left == NULL && temp.first->right == NULL && temp.second == sum)
                return true;
            //左节点入栈
            if(temp.first->left != NULL) 
                s.push({temp.first->left, temp.second + temp.first->left->val});
            //右节点入栈
            if(temp.first->right != NULL) 
                s.push({temp.first->right, temp.second + temp.first->right->val});
        }
        return false;
    }
};

 BM30.二叉搜索树与双向链表

题目描述: 

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。如下图所示

数据范围:输入二叉树的节点数 0≤n≤1000,二叉树中每个节点的值  0≤val≤1000
要求:空间复杂度O(1)(即在原树上操作),时间复杂度O(n)

注意:

1.要求不能创建任何新的结点,只能调整树中结点指针的指向。当转化完成以后,树中节点的左指针需要指向前驱,树中节点的右指针需要指向后继
2.返回链表中的第一个节点的指针
3.函数返回的TreeNode,有左右指针,其实可以看成一个双向链表的数据结构

4.你不用输出双向链表,程序会根据你的返回值自动打印输出

输入描述:

二叉树的根节点

返回值描述:

双向链表的其中一个头节点。

方法一:递归中序遍历(推荐使用)

知识点1:二叉树递归

递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。

而二叉树的递归,则是将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。

知识点2:二叉搜索树

二叉搜索树是一种特殊的二叉树,它的每个节点值大于它的左子节点,且大于全部左子树的节点值,小于它右子节点,且小于全部右子树的节点值。因此二叉搜索树一定程度上算是一种排序结构。

思路:

二叉搜索树最左端的元素一定最小,最右端的元素一定最大,符合“左中右”的特性,因此二叉搜索树的中序遍历就是一个递增序列,我们只要对它中序遍历就可以组装称为递增双向链表。

具体做法:

  • step 1:创建两个指针,一个指向题目中要求的链表头(head),一个指向当前遍历的前一节点(pre)。
  • step 2:首先递归到最左,初始化head与pre。
  • step 3:然后处理中间根节点,依次连接pre与当前节点,连接后更新pre为当前节点。
  • step 4:最后递归进入右子树,继续处理。
  • step 5:递归出口即是节点为空则返回。
class Solution {
public:
    //返回的第一个指针,即为最小值,先定为NULL
    TreeNode* head = NULL;  
    //中序遍历当前值的上一位,初值为最小值,先定为NULL
    TreeNode* pre = NULL;   
    TreeNode* Convert(TreeNode* pRootOfTree) {
        if(pRootOfTree == NULL)
            //中序递归,叶子为空则返回
            return NULL;     
        //首先递归到最左最小值   
        Convert(pRootOfTree->left); 
        //找到最小值,初始化head与pre
        if(pre == NULL){       
            head = pRootOfTree;
            pre = pRootOfTree;
        }
        //当前节点与上一节点建立连接,将pre设置为当前值
        else{       
            pre->right = pRootOfTree;
            pRootOfTree->left = pre;
            pre = pRootOfTree;
        }
        Convert(pRootOfTree->right);
        return head;
    }
};

方法二:非递归中序遍历(扩展思路)

知识点:栈

栈是一种仅支持在表尾进行插入和删除操作的线性表,这一端被称为栈顶,另一端被称为栈底。元素入栈指的是把新元素放到栈顶元素的上面,使之成为新的栈顶元素;元素出栈指的是从一个栈删除元素又称作出栈或退栈,它是把栈顶元素删除掉,使其相邻的元素成为新的栈顶元素。

思路:

二叉树中序遍历除了递归方法,我们还可以尝试非递归解法,与常规的非递归中序遍历几乎相同,还是借助栈来辅助,只是增加了连接节点。

具体做法:

  • step 1:创建两个指针,一个指向题目中要求的链表头(head),一个指向当前遍历的前一节点(pre),创建一个布尔型变量,标记是否是第一次到最左,因为第一次到最左就是链表头。
  • step 2:判断空树不能连接。
  • step 3:初始化一个栈辅助中序遍历。
  • step 4:依次将父节点加入栈中,直接进入二叉树最左端。
  • step 5:第一次进入最左,初始化head与pre,然后进入它的根节点开始连接。
  • step 6:最后将右子树加入栈中,栈中依次就弹出“左中右”的节点顺序,直到栈为空。
class Solution {
public:
    TreeNode* Convert(TreeNode* pRootOfTree) {
        if (pRootOfTree == NULL) {
            return NULL;
        }
        //设置栈用于遍历
        stack<TreeNode*> s; 
        TreeNode* head = NULL;
        TreeNode* pre = NULL;
        //确认第一个遍历到最左,即为首位
        bool isFirst = true; 
        while(pRootOfTree != NULL || !s.empty()){
            //直到没有左节点
            while(pRootOfTree != NULL){   
                s.push(pRootOfTree);
                pRootOfTree = pRootOfTree->left;
            }
            pRootOfTree = s.top();
            s.pop();
            //首位
            if(isFirst){  
                head = pRootOfTree;
                pre = head;
                isFirst = false;
            //当前节点与上一节点建立连接,将pre设置为当前值
            }else{          
                pre->right = pRootOfTree;
                pRootOfTree->left = pre;
                pre = pRootOfTree;
            }
            pRootOfTree = pRootOfTree->right;
        }
        return head;
    }
};

 BM31.对称的二叉树

方法一:递归(推荐使用)

知识点:二叉树递归

递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。

而二叉树的递归,则是将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。

思路:

前序遍历的时候我们采用的是“根左右”的遍历次序,如果这棵二叉树是对称的,即相应的左右节点交换位置完全没有问题,那我们是不是可以尝试“根右左”遍历,按照轴对称图像的性质,这两种次序的遍历结果应该是一样的。

不同的方式遍历两次,将结果拿出来比较看起来是一种可行的方法,但也仅仅可行,太过于麻烦。我们不如在遍历的过程就结果比较了。而遍历方式依据前序递归可以使用递归:

  • 终止条件: 当进入子问题的两个节点都为空,说明都到了叶子节点,且是同步的,因此结束本次子问题,返回true;当进入子问题的两个节点只有一个为空,或是元素值不相等,说明这里的对称不匹配,同样结束本次子问题,返回false。
  • 返回值: 每一级将子问题是否匹配的结果往上传递。
  • 本级任务: 每个子问题,需要按照上述思路,“根左右”走左边的时候“根右左”走右边,“根左右”走右边的时候“根右左”走左边,一起进入子问题,需要两边都是匹配才能对称。

具体做法:

  • step 1:两种方向的前序遍历,同步过程中的当前两个节点,同为空,属于对称的范畴。
  • step 2:当前两个节点只有一个为空或者节点值不相等,已经不是对称的二叉树了。
  • step 3:第一个节点的左子树与第二个节点的右子树同步递归对比,第一个节点的右子树与第二个节点的左子树同步递归比较。
class Solution {
public:
    bool zuoyou(TreeNode* root1, TreeNode* root2){
        //终止条件
        if(root1 == nullptr && root2 == nullptr) return true;
        if(root1 == nullptr || root2 == nullptr || root1->val != root2->val )
            return false;
        return (zuoyou(root1->left, root2->right) && zuoyou(root1->right, root2->left));
    }
    bool isSymmetrical(TreeNode* pRoot) {
        //终止条件
        if(pRoot == nullptr) return true;
        else
            return zuoyou(pRoot->left, pRoot->right);
        /*
        bool isSymmetrical(TreeNode* pRoot) {
            return recursion(pRoot, pRoot);
        }
        */
    }
};

 BM32.合并二叉树

方法一:递归前序遍历(推荐使用)

知识点:二叉树递归

递归是一个过程或函数在其定义或说明中有直接或间接调用自身的一种方法,它通常把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解。因此递归过程,最重要的就是查看能不能讲原本的问题分解为更小的子问题,这是使用递归的关键。

而二叉树的递归,则是将某个节点的左子树、右子树看成一颗完整的树,那么对于子树的访问或者操作就是对于原树的访问或者操作的子问题,因此可以自我调用函数不断进入子树。

思路:

要将一棵二叉树的节点与另一棵二叉树相加合并,肯定需要遍历两棵二叉树,那我们可以考虑同步遍历两棵二叉树,这样就可以将每次遍历到的值相加在一起。遍历的方式有多种,这里推荐前序递归遍历。

具体做法:

  • step 1:首先判断t1与t2是否为空,若为则用另一个代替,若都为空,返回的值也是空。
  • step 2:然后依据前序遍历的特点,优先访问根节点,将两个根点的值相加创建到新树中。
  • step 3:两棵树再依次同步进入左子树和右子树。
class Solution {
public:
    TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) {
        //若只有一个节点返回另一个,两个都为NULL自然返回NULL
        if (t1 == NULL) 
            return t2;
        if (t2 == NULL)
            return t1;
        //根左右的方式递归
        TreeNode* head = new TreeNode(t1->val + t2->val);
        head->left = mergeTrees(t1->left, t2->left);
        head->right = mergeTrees(t1->right, t2->right);
        return head;
    }
};

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值