树 Leetcode #104 二叉树的最大深度 Leetcode #110 平衡二叉树 Leetcode #543 二叉树的直径 Leetcode #543 翻转二叉树 Leetcode #543 另一个树的子树 Leetcode #543 对称二叉树 Leetcode #669 修剪二叉搜索树 Leetcode #108 将有序数组转换为二叉搜索树 Leetcode #109 有序链表转换二叉搜索树 Leetcode #653 两数之和 IV - 输入 BST Leetcode #501 二叉搜索树中的众数 Leetcode #637 二叉树的层平均值 Leetcode #513 找树左下角的值 Leetcode #1367 二叉树中的列表 Leetcode #951 翻转等价二叉树

题名:二叉树的最大深度
描述:
给定一个二叉树,找出其最大深度。

二叉树的深度为根节点到最远叶子节点的最长路径上的节点数。

说明: 叶子节点是指没有子节点的节点。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/maximum-depth-of-binary-tree/

方法:

树的问题大多可用递归解决,这题也不例外。
要找该二叉树的最大深度,其实就是找其左右子树最大深度较大的那个在加1,就是该二叉树的最大深度,依次递归即可。代码如下:


int maxDepth(TreeNode* root) {
        //递归出口
        if(!root)    return 0;
        //递归式
        return max(maxDepth(root->left), maxDepth(root->right)) + 1;
    }

Leetcode #110 平衡二叉树

题名:平衡二叉树
描述:给定一个二叉树,判断它是否是高度平衡的二叉树。

说明: 一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/balanced-binary-tree/

树的解题思路一般用递归,因为用递归思想比较容易想到解题的具体方法,也可以减少代码量。当然有得必有失嘛,会牺牲一些空间效率,甚至是时间效率。
判断这棵二叉树是否是平衡的,即遍历每一个树节点,查看该节点的左右子树最大深度之差是否不超过1。

    bool isb = true;
    bool isBalanced(TreeNode* root) {
        if(!root)   return isb;//出口就在当结点为空时,当然空节点是平衡的!
        int dl = maxDepth(root->left);
        int dr = maxDepth(root->right);
        if(abs(dl - dr) > 1)//进行剪枝,如果有一个结点不满足,就可以返回false了!    
        {
            isb = false;
            return isb;
        }
        isBalanced(root->left);//左子树是否为平衡的?
        isBalanced(root->right);//右子树是否为平衡的?
        return isb;
    }
    //求一结点的最大深度
    int maxDepth(TreeNode* root) {
        if(!root)    return 0;
        return max(maxDepth(root->left), maxDepth(root->right)) + 1;
    }

Leetcode #543 二叉树的直径

题名:diameterofBinaryTree
描述:给定一棵二叉树,你需要计算它的直径长度。一棵二叉树的直径长度是任意两个结点路径长度中的最大值。这条路径可能穿过根结点。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/balanced-binary-tree/

方法:树的最大深度 + 树的遍历

这道题求最大直径,可能刚开始会遇到的一个困难点在于这条路径可能不穿过根结点这个可能性。如果改成这条路径必定穿过根结点,那么我相信小伙伴们都会做,只要求该根结点的左右子树的最大深度,再相加即可。但是现在不一定穿过树根结点呀~这可怎么办呀~~

其实虽然题目这么说了:这条路径可能穿过根结点。但在我看了,它却是这条路径必定穿过根结点。我为什么这么说呢?因为此根非彼根,我们认为的根就是最顶层那个,有且仅有一个。但是记住了,树这个数据结构本身就是递归定义的,任一个二叉树的左右子结点是其左右子树的根!!!所以如果仅仅把一整颗大的二叉树看出一个整体,的确,最长路径可能不穿过根结点,但是如果仅仅把最长路径走过的最高的那个顶点看做根结点,那么最长路径是永远穿过根结点的!!!

1.这道题其实就是先序遍历每个节点,对每个节点求其左右子树深度再相加得到一个最长路径
2.返回最长的那个最长路径
上代码:


    int diameterOfBinaryTree(TreeNode* root) {
        if(!root) return 0;
        int g = length_l_r(root);
        int l = diameterOfBinaryTree(root->left);
        int r = diameterOfBinaryTree(root->right);
        return max(g, max(l, r));//取三者中最大的

    }
    int length_l_r(TreeNode *root)//求某个子二叉树的最长路径
    {
        return maxDepth(root->left) + maxDepth(root->right);
    }
    int maxDepth(TreeNode *root)//求最大深度
    {
        if(!root) return 0;
        return max(maxDepth(root->left), maxDepth(root->right)) + 1;
    }

Leetcode #543 翻转二叉树

题名:翻转二叉树
描述:翻转一棵二叉树。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/invert-binary-tree/

题目的要求是将树翻转(修改要落实到结点上),而不是打印输出是翻转二叉树。
用递归马上就解决了,代码如下:


    TreeNode* invertTree(TreeNode* root) {
        if(!root)   return root;
        TreeNode *root_l = invertTree(root->left);
        TreeNode *root_r = invertTree(root->right);
        root->left = root_r;
        root->right = root_l;
        return root;
    }

Leetcode #543 另一个树的子树

题名:另一个树的子树
描述:给定两个非空二叉树 s 和 t,检验 s 中是否包含和 t 具有相同结构和节点值的子树。s 的一个子树包括 s 的一个节点和这个节点的所有子孙。s 也可以看做它自身的一棵子树。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/subtree-of-another-tree/

方法:

这个问题可以用递归解决,总结如下:一棵树B是另一颗树A的子树,那么有三种情况1.树A 与 树B完全相等 2.树B是树A右树的子树 3.树B是树A左树的子树;以此递归
这题要两处递归,一处是判断B树是否为A树的子树的函数需要递归,还有一处是判断两颗树是否相等的函数需要递归;


/*************判断是否是子树*************/
    bool isSubtree(TreeNode* s, TreeNode* t) {
        if(s == NULL && t == NULL)  return true;
        if(s == NULL && t != NULL)   return false;
        return isSameTree(s, t)//如果t是s的子树,那么要么两棵树相等,要么t是s右树的子树或是左树的子树;
               || isSubtree(s->left, t)
               || isSubtree(s->right, t);
    }

判断两棵树是否相等:首先若相等则树s,树t的根结点都存在,并且值相等,并且左右子树各自都相等;


/*************判断两棵树是否相等*************/
    bool isSameTree(TreeNode *s, TreeNode *t){
        if(s == NULL && t == NULL)  return true;
        return s && t 
               && s->val == t->val 
               && isSameTree(s->left, t->left) 
               && isSameTree(s->right, t->right);
    }

Leetcode #543 对称二叉树

题名:对称二叉树
描述:给定一个二叉树,检查它是否是镜像对称的。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/symmetric-tree/

方法:

如果一颗二叉树是镜像对称的,那么它的左子树与右子树也是镜像对称的,以此递归:


    bool isSymmetric(TreeNode *l, TreeNode *r){
        if(!l && !r)    return true;
        if(l == NULL || r == NULL)  return false;
        if(l->val != r->val)    return false;
        return isSymmetric(l->left, r->right) && isSymmetric(l->right, r->left);
    }
    bool isSymmetric(TreeNode* root) {
        if(!root)   return true;
        return isSymmetric(root->left, root->right);
    }

Leetcode #669 修剪二叉搜索树

题名:修剪二叉搜索树
描述:给定一个二叉搜索树,同时给定最小边界L 和最大边界 R。通过修剪二叉搜索树,使得所有节点的值在[L, R]中 (R>=L) 。你可能需要改变树的根节点,所以结果应当返回修剪好的二叉搜索树的新的根节点。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/trim-a-binary-search-tree/

方法:

又是老掉牙的递归思想;
1.返回值是已经修剪过的二叉搜索树的根结点;
2.递归边界是空节点;
3.递归式就要考虑具体情况了,对于每一个树节点root(可以看成每颗子树的根结点)都有三种情况:1.root->val > R; 2.root->val <L; 3.root->val >= L &&root->val <= R 。那么对于第一种情况,因为root节点值大于右边界,所以我们不需要再考虑其右子树了(因为右子树的值都大于该root节点的值),只需要将经过修剪的root左子树的根结点(注意这个根是root左子树的根而非root本身)赋值给root即可(也就是将不符合要求的root覆盖啦~);对于第二种情况同理;对于第三种情况,也就是符合要求的root结点,我们就不需要将其覆盖了,而是对其左右子树进行递归操作即可。代码如下:


TreeNode* trimBST(TreeNode* root, int L, int R) {
        if(!root)   return NULL;
        if(root->val > R)
            root = trimBST(root->left, L, R);
        else if(root->val < L)
            root = trimBST(root->right, L, R);
        else{
            root->left = trimBST(root->left, L, R);
            root->right = trimBST(root->right, L, R);
        }
        return root;
    }

Leetcode #108 将有序数组转换为二叉搜索树

题名:将有序数组转换为二叉搜索树
描述:将一个按照升序排列的有序数组,转换为一棵高度平衡二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/convert-sorted-array-to-binary-search-tree/

方法:

要求做到BST是平衡的,并且题目给出了有序数组。我们要做到的其实就是:1.找到有序数组中间的那个元素,将其作为根结点。2.把中间元素的两边的元素作为其左右子树。这样左子树结点个数必定等于右子树结点个数。3.进行递归构造BST。可以推出这样构造出来的BST其左右子树高度差必定小于等于1;代码如下:


TreeNode* sortedArrayToBST(vector<int>& nums) {
        return toBST(nums, 0, nums.size() - 1);
    }
    TreeNode *toBST(const vector<int> &nums, int sIdx, int eIdx){
        if(sIdx > eIdx) return NULL;
        int mid = (sIdx + eIdx + 1) / 2;//这里+1是为了保证每次取有序数组中间偏右的那个元素;当然也可以选择不加,那么默认就是中间偏左的元素
        TreeNode *root = new TreeNode(nums[mid]);
        root->left = toBST(nums, sIdx, mid - 1);
        root->right = toBST(nums, mid + 1, eIdx);
        return root;
    } 

Leetcode #109 有序链表转换二叉搜索树

题名:有序链表转换二叉搜索树
描述:给定一个单链表,其中的元素按升序排序,将其转换为高度平衡的二叉搜索树。

本题中,一个高度平衡二叉树是指一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过 1

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/convert-sorted-list-to-binary-search-tree/

方法1:同108题

这题和108题类似,只不过从有序数组变成了有序链表了。那么当然可以用简单粗暴的方法,把链表中的元素存储到数组中,再用108题的方法进行构造BST即可;

方法2:快慢指针

重点:通过快慢指针,一次遍历就能找到链表的中间元素!

1.由于我们得到的是一个有序链表而不是数组,我们不能直接使用下标来访问元素。我们需要知道链表中的中间元素。
2.我们可以利用两个指针来访问链表中的中间元素。假设我们有两个指针 slow_ptrfast_ptrslow_ptr 每次向后移动一个节点而 fast_ptr 每次移动两个节点。当 fast_ptr 到链表的末尾时 slow_ptr 就访问到链表的中间元素。对于一个偶数长度的数组,中间两个元素都可用来作二叉搜索树的根。
3.当找到链表中的中间元素后,我们将链表从中间元素的左侧断开,做法是使用一个 prev_ptr 的指针记录 slow_ptr 之前的元素,也就是满足 prev_ptr.next = slow_ptr。断开左侧部分就是让 prev_ptr.next = None
4.我们只需要将链表的头指针传递给转换函数,进行高度平衡二叉搜索树的转换。所以递归调用的时候,左半部分我们传递原始的头指针;右半部分传递 slow_ptr.next 作为头指针。

代码如下:


TreeNode* sortedListToBST(ListNode* head){
        return toBST(head);
    }
    TreeNode *toBST(ListNode *head){
        ListNode *low_ptr = head, *fast_ptr = head, *pre;
        if(!head) return NULL;//当链表中一个元素都没有时,返回空
        else if(!head->next){//当链表中只有一个元素时,无需进行分割了,直接返回即可
            TreeNode *root = new TreeNode(head->val);//构造一个跟结点
            return root;
        }
        while(fast_ptr && fast_ptr->next){//一定要判断fast_ptr->next是否为空;若为空fast_ptr->next->next会发生段错误!
            pre = low_ptr;
            low_ptr = low_ptr->next;
            fast_ptr = fast_ptr->next->next;//
        }
        TreeNode *root = new TreeNode(low_ptr->val);
        pre->next = NULL;//断开链表
        root->left = toBST(head);
        root->right = toBST(low_ptr->next);
        return root;
    }

Leetcode #653 两数之和 IV - 输入 BST

题名:两数之和 IV - 输入 BST
描述:给定一个二叉搜索树和一个目标结果,如果 BST 中存在两个元素且它们的和等于给定的目标结果,则返回 true

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/two-sum-iv-input-is-a-bst/

方法:

我们可以通过中序遍历把BST中的元素按升序存储到数组中,这样就把问题转化成了在一个升序数组中是否可以找到2个元素使其和等于给定的目标数,若存在返回true,反之返回false;有两种方法来处理2元素和等于目标值得问题,一种是暴力法;另一种是双指针法;代码如下:


bool findTarget(TreeNode* root, int k) {
        vector<int> vi;
        traversal(root, vi, k);
        int l_ptr = 0, r_ptr = vi.size() - 1;
        // for(int i = 0;i < vi.size();i++){//暴力法, 时间复杂度 O(n*n)
        //     for(int j = i + 1;j < vi.size();j++){
        //         if(vi[i] + vi[j] == k)    return true;
        //     }
        // }
        while(l_ptr < r_ptr){//双指针法,时间复杂度 O(n)
            if(vi[l_ptr] + vi[r_ptr] == k)  return true;
            else if(vi[l_ptr] + vi[r_ptr] < k)  l_ptr++;
            else   r_ptr--;
        }
        return false;
    }
    void traversal(TreeNode *root, vector<int> &vi, int k){
        if(!root)   return;
        traversal(root->left, vi, k);
        vi.push_back(root->val);
        traversal(root->right, vi, k);
    }

Leetcode #501 二叉搜索树中的众数

题名:二叉搜索树中的众数
描述:给定一个有相同值的二叉搜索树(BST),找出 BST 中的所有众数(出现频率最高的元素)。

假定 BST 有如下定义:

结点左子树中所含结点的值小于等于当前结点的值
结点右子树中所含结点的值大于等于当前结点的值
左子树和右子树都是二叉搜索树
例如:
给定 BST [1,null,2,2],

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/find-mode-in-binary-search-tree/

方法:中序遍历

这个方法不需要额外内存空间 (除了递归栈)

根据BST的特性,对其中序遍历得到的值是递增的这个原则,我们看一颗BST其实就是在看一个递增数组。那么现在问题转化成如何在递增数组中找众树。
例如有递增数组a[5] = {1, 2, 2, 3, 3};我们可以设置一个 preValcurVal 值刚开始分别是1和2,然后向后移动,直到数组边界;在移动的过程中用 curN 记录 preVal 出现的次数, 用 maxN 记录某个众数出现的次数。当 preVal == curValcurN++ ;当 preVal != curVal 时比较 curNmaxN 的值,考虑是否出现了众数;
但是我们现在是BST而不是数组怎么办呢?我之前有说其实BST通过中序遍历就可以看出一个递增数组,代码如下:


class Solution {
private:
    int maxn = 0;//用于记录众数出现的次数
public:
    vector<int> findMode(TreeNode* root) {
        vector<int> ans;
        if(!root)   return ans;
        int curn = 0, preVal;
        maxn = 0;
        TreeNode *p = root;
        while(p->left){//先找到preVal,也就时递增数组中最小的那个元素
            p = p->left;
        }
        preVal = p->val;
        traversal(root, ans, curn, preVal);
        if(curn == maxn)   ans.push_back(preVal);//以防递增数组中最后一个元素与preVal相等
        else if(curn > maxn){
            maxn = curn;
            ans.clear();
            ans.push_back(preVal);
        }
        return ans;
    }
    void traversal(TreeNode *root, vector<int> &ans, int &curn, int &preVal){
        if(!root)   return;
        traversal(root->left, ans, curn, preVal);
        if(preVal == root->val) curn++;
        else{
            if(curn == maxn)   ans.push_back(preVal);
            else if(curn > maxn){
                maxn = curn;
                ans.clear();
                ans.push_back(preVal);
            }
            curn = 1;
        }
        preVal = root->val;
        traversal(root->right, ans, curn, preVal);
    }
};

Leetcode #637 二叉树的层平均值

题名: 二叉树的层平均值
描述:给定一个非空二叉树, 返回一个由每层节点平均值组成的数组.

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/average-of-levels-in-binary-tree/

方法:层序遍历


vector<double> averageOfLevels(TreeNode* root) {
        vector<double> ans;
        queue<TreeNode *> q;
        q.push(root);
        int count = 0, guard = 1, pos = 0;
        long long sum = 0;
        while(!q.empty()){
            TreeNode *node = q.front();
            q.pop();
            pos++;count++;
            sum += node->val;
            if(node->left)  q.push(node->left);
            if(node->right) q.push(node->right);
            if(pos == guard){
                guard += q.size();
                ans.push_back(sum * 1.0 / count);
                sum = 0;
                count = 0;
            }
        }
        return ans;
    }

Leetcode #513 找树左下角的值

题名: 找树左下角的值
描述:给定一个二叉树,在树的最后一行找到最左边的值。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/find-bottom-left-tree-value/

方法:层序遍历

层序遍历的题套用模板就行,再加一点判断什么的基本就解决问题了!


int findBottomLeftValue(TreeNode* root) {
        queue<TreeNode *> q;
        q.push(root);
        TreeNode *leftGuy = NULL;
        int guard = 1, pos = 0, ans = root->val;
        while(!q.empty()){
            TreeNode *node = q.front();
            q.pop();
            pos++;
            if(node->left)  q.push(node->left);
            if(node->right) q.push(node->right);
            if(pos == guard){
                guard += q.size();
                if(!q.empty()){
                    TreeNode *node = q.front();
                    ans = node->val;
                } 
            }
        }
        return ans;
    }

Leetcode #1367 二叉树中的列表

题名: 二叉树中的列表
描述:给你一棵以 root 为根的二叉树和一个 head 为第一个节点的链表。

如果在二叉树中,存在一条一直向下的路径,且每个点的数值恰好一一对应以 head 为首的链表中每个节点的值,那么请你返回 True ,否则返回 False

一直向下的路径的意思是:从树中某个节点开始,一直连续向下的路径。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/linked-list-in-binary-tree/

实例1:

(具体的图请看Leetcode相关网页)
输入:head = [4,2,8], root = [1,4,4,null,2,2,null,1,null,6,8,null,null,null,null,1,3]
输出:true

方法1:KMP中的next数组 + 先序遍历:

  • 这个问题实际上是对数字串的匹配,模式串已经给出,就是 head 链表中的数,将其放入数组中,方便之后的操作。然后根据模式串得出 next 数组。
  • 得到 next 数组后,我们找到一旦模式串与二叉树中的子串不匹配时,模式串应当右移几个位置(相当于模式串的索引后退几个位置),得到调整后的索引之后继续遍历。

代码如下:

    bool isSubPath(ListNode* head, TreeNode* root) {
        ListNode *curr = head, *p = head;
        vector<int> vi; //将链表中的元素存入数组,方便之后的操作
        while(p){
            vi.push_back(p->val);
            p = p->next;
        }
        vector<int> next;
        KMP(next, vi);//得到模式串的 next 数组
        return isSubPath(vi, next, 0, root);
    }
    void KMP(vector<int> &next, vector<int> &vi){
        next.resize(vi.size());
        if(next.size() == 1){
            next[0] = -1;
            return;
        }
        next[1] = 0;
        int len = vi.size();
        int i = 2, x = 0;
        while(i < len){
            if(vi[i - 1] == vi[x])
                next[i++] = ++x;
            else if(x > 0)
                x = next[x];
            else    next[i++] = 0;
        }
    }
    bool isSubPath(const vector<int> &vi, const vector<int> &next, int index, TreeNode *root){
        if(index == vi.size())   return true; //如果模式串全部得到匹配则说明我们找到了答案
        if(!root)   return false; //如果访问到叶子节点后还没有找到答案说明这条路径是错误的
        if(vi[index] == root->val)  index++; 
        else{ //如果模式串和二叉树子串不匹配
            index = next[index];
            if(index < 0)   index = 0; //得到对应的 index 值
            if(vi[index] == root->val)  index++; //这里必须要判断一下
        } 
        return isSubPath(vi, next, index, root->left) || isSubPath(vi, next, index, root->right); //只要二叉树的左子树或右子树中的子串与模式串匹配则返回true
    }

Leetcode #951 翻转等价二叉树

题名: 翻转等价二叉树
描述:我们可以为二叉树 T 定义一个翻转操作,如下所示:选择任意节点,然后交换它的左子树和右子树。

只要经过一定次数的翻转操作后,能使 X 等于 Y,我们就称二叉树 X 翻转等价于二叉树 Y。

编写一个判断两个二叉树是否是翻转等价的函数。这些树由根节点 root1 和 root2 给出。

具体描述请查看Leetcode相关网页:https://leetcode-cn.com/problems/flip-equivalent-binary-trees/

实例1:

输入:root1 = [1,2,3,4,5,6,null,null,null,7,8], root2 = [1,3,2,null,6,4,5,null,null,null,null,8,7]
输出:true
解释:我们翻转值为 1,3 以及 5 的三个节点。
(具体图请见 `https://leetcode-cn.com/problems/flip-equivalent-binary-trees/` )

方法:递归


  • 如果两棵树 A, B 是翻转等价二叉树,那么 A 的根节点的值等于 B 的根节点的值,并且 A 的左子树与 B 的左子树或右子树是反转等价二叉树;A 的右子树与 B 的左子树或右子树是反转等价二叉树;

代码如下:

    bool flipEquiv(TreeNode* root1, TreeNode* root2) {
        if((!root1 && root2) || (root1 && !root2))  return false;
        else if(!root1 && !root2)   return true;
        else if(root1->val != root2->val)   return false;

        return (flipEquiv(root1->left, root2->left) || flipEquiv(root1->left, root2->right)) && 
                (flipEquiv(root1->right, root2->left) || flipEquiv(root1->right, root2->right));
        
    }