leetcode之二叉树类之二叉树深度系列-----104/111/110/108/109 二叉树最大/最小深度/AVL树的判断和由有序序列生成(牵扯分治相关,OJ105/106)

二叉树的最大、最小深度,首先明确一点,不论最大还是最小,二叉树的深度都是基于leaf节点的,即二叉树的leaf节点的层数可以称之为深度,而求最大深度或最小深度,就是找深度最大是多少,最小是多少,前提必须是合法的深度。

同样是到leaf才见分晓的判断,走先序遍历,到达leaf就返回深度,而求最大或最小深度,就是返回左深度和右深度的max或min

利用极端例子思考,一个二叉树,root的左子树有很多层节点,右子树只有一个节点,那么最大深度是左子树到最深叶子的层数,最小深度就是2

如果root只有一侧有节点,如左或右某一侧为空,那么最小深度为2,最大深度依然需要计算

如果root没有任何子节点,那么最大深度和最小深度都是1

OJ104代码:

class Solution {
public:
    int helper (TreeNode *cur, int h) {
        if (cur) {
            if (!cur->left && !cur->right) {
                return h;
            } else {
                if (cur->left && cur->right) {
                    int l = helper(cur->left, h + 1);
                    int r = helper(cur->right, h + 1);
                    return std::max(l, r);
                } else {
                    if (cur->left) {
                        return helper(cur->left, h + 1);
                    }
                    if (cur->right) {
                        return helper(cur->right, h + 1);
                    }
                }
            }
        }
    }
    
    int maxDepth(TreeNode* root) {
        if (!root) {
            return 0;
        } else if (!root->left && !root->right) {
            return 1;
        }
        
        return helper(root, 1);
    }
};

OJ111代码:

class Solution {
public:
    int helper (TreeNode *cur, int h) {
        if (cur) {
            if (!cur->left && !cur->right) {
                return h;
            } else {
                if (cur->left && cur->right) {
                    int l = helper(cur->left, h + 1);
                    int r = helper(cur->right, h + 1);
                    return std::min(l, r);
                } else {
                    if (cur->left) {
                        return helper(cur->left, h + 1);
                    } else {
                        return helper(cur->right, h + 1);
                    }
                }
            }
        } else {
            return h - 1;
        }
    }
    
    int minDepth(TreeNode* root) {
        if (!root) {
            return 0;
        } else if (!root->left && !root->right) {
            return 1;
        }
        
        return helper(root, 1);
    }
};

OJ110,判断一个搜索二叉树是否为AVL树,AVL树的定义是,任意一个节点的左子树和右子树的深度差别不能超过1,所以需要对每一个节点,求出左子树深度和右子树深度,典型需要后序遍历,然后判断是否出现不符合要求case

递归函数的返回值需要用于返回树的深度,所以可以传一个引用来保存不符合要求的时候。这是二叉树题的典型方式。

OJ110代码:

class Solution {
public:
    int helper (TreeNode *cur, int h, bool &res) {
        if (cur) {
            int left = helper(cur->left, h + 1, res);
            int right = helper(cur->right, h + 1, res);
            
            if (left - right >= 2 || right - left >= 2) {
                res = false;
            }
            return (left > right)?left:right;
        } else {
            return h - 1;
        }
    }
    
    bool isBalanced(TreeNode* root) {
        if (!root) {
            return true;
        }
        
        bool res = true;
        helper(root, 0, res);
        return res;
    }
};

OJ108和OJ109都是给定一个有序序列,然后生成AVL树,方法都显然是以该序列的不断的二分,生成AVL树一层层的节点,差异是OJ108是数组,可以直接取索引定位mid节点及左右范围,而OJ109是链表,总需要通过快慢指针找mid节点;这两道题考察重点为分治;

注意有些题的题意与OJ108不同,但也是基于分治,如”由先序遍历序列和中序遍历序列,或由后序遍历序列和中序遍历序列,生成二叉树”(OJ105/106);而OJ109在考察AVL树原理之外,更多考察的是单链表的快慢指针找中点和边界处理细节,OJ109比较容易做错,在实战中可以作为一道比较恶心的题出现;

OJ108,给定有序数组生成AVL树,不断二分找mid节点生成每一层的,进而用左半部分生成左子树,右半部分生成右子树

OJ108代码:

class Solution {
public:
    void helper (vector<int> nums, TreeNode *&cur, int st, int ed) {
        if (st == ed) {
            cur = new TreeNode(nums[st]);
            return;
        } else if (st > ed) {
            return;
        }
        
        int mid = (st + ed)/2;
        cur = new TreeNode(nums[mid]);
        helper(nums, cur->left, st, mid - 1);
        helper(nums, cur->right, mid + 1, ed);
    }
    
    TreeNode* sortedArrayToBST(vector<int>& nums) {
        TreeNode *root = nullptr;
        if (nums.empty()) {
            return root;
        }
        
        int st = 0, ed = nums.size() - 1;
        helper(nums, root, st, ed);
        return root;
    }
};

OJ109,和OJ108唯一区别是由有序数组变为有序链表,麻烦点在于找中点更麻烦

我的可以AC的方法是,用一个函数做链表的二分,同时将原先链表分裂为两个新链表,即mid左边节点如果存在的话,不再指向mid而是指向nullptr,这里要注意mid左边没有节点的时候,head其实是和mid是一个节点,如果不做判断mid和head是否相同就继续处理,会出现重复不停的生成左子树的节点

程序总体结构和OJ108是差不多的,唯一就是多了找中点部分

OJ109代码:

class Solution {
public:
    ListNode *GetMid (ListNode *head, ListNode *&right) {
        if (!head || !head->next || !head->next->next) {
            if (head->next) {
                right = head->next;
            }
            return head;
        }
        
        ListNode *cur = head, *prev = head, *pprev = nullptr;
        while (cur && cur->next && cur->next->next) {
            pprev = prev;
            prev = prev->next;
            cur = cur->next->next;
        }
        
        if (pprev) {
            pprev->next = nullptr;
        }
        right = prev->next;
        prev->next = nullptr;
        return prev;
    }
    
    void helper (ListNode *head, TreeNode *&cur) {
        if (!head) {
            return;
        }
        
        ListNode *right = nullptr;
        ListNode *mid = GetMid(head, right);
        if (mid) {
            cur = new TreeNode(mid->val);
            if (mid != head) {
                helper(head, cur->left);
            }
            helper(right, cur->right);
        }
    }
    
    TreeNode* sortedListToBST(ListNode* head) {
        TreeNode *root = nullptr;
        if (!head) {
            return root;
        }
        
        helper(head, root);
        return root;
    }
};

OJ105和OJ106,由先序+中序,或由后序+中序,生成对应的二叉树,这两个题考察点和OJ108/OJ109不同,它们更侧重二叉树的深度遍历,但解题办法很相似,在这一并记录一下;

核心思路是:

1、由中序遍历自己无法还原对应的二叉树,因为中序遍历反映不了左子树右子树的顺序,如中序遍历123,可以是

     1
      \
       2
        \
         3

但也可以是:

     1
      \
       3
      /
     2

到底是哪个,这就需要先序或后序遍历,比如对于第1个图,先序遍历是123,后序遍历是321,对于第2个图先序遍历是132,后序遍历是321

2、先序遍历或后序遍历怎么样和中序遍历一起生成对应的二叉树

方法:临场在纸上画就行,如随意画下图:

     3
    / \
   2   5
  /   / \
 1   4   6

先序遍历是321546,中序遍历是123456,后序遍历是124653

同时看先序+中序和后序+中序,步骤:

1、对于先序+中序,第0个节点是root,对于后序+中序,最后一个节点是root

2、生成了root后,中序遍历中,root节点的左半部分就是左子树,右半部分是右子树,对于图中例子就是,12是左子树,456是右子树

      对于左子树12,先序中第0个节点是2,则2是左子树12的头节点,而在后序中最后一个节点是2,2是头节点

      对于右子树456,同理,4是头节点

如果已经看了前面OJ108/OJ109的解法,看到这里应该知道程序大概怎么写,可见这道题同样属于分治,具体的,不论是先序+中序,还是后序+中序,每次定位到头节点并生成当前节点后,定位先序或后序的左子树、右子树部分,具体来说就是定位左子树、右子树部分,在数组的首尾索引,不断继续递归生成节点,递归停止条件就是左子树、右子树部分为空

OJ105代码:

class Solution {
public:
    void helper (const vector<int> &preorder, const vector<int> &inorder, int prest, int preed, int midst, int mided, TreeNode *&cur) {
        if (!(prest <= preed && midst <= mided && mided - midst == preed - prest && preed < preorder.size() && midst < inorder.size())) {
            return;
        }
        
        cur = new TreeNode(preorder[prest]);
        if (prest == preed && midst == mided) {
            return;
        }
        
        int count = 0;
        int mididx;
        for (int i = midst; i <= mided; i++) {
            if (preorder[prest] == inorder[i]) {
                mididx = i;
                break;
            } else {
                ++count;
            }
        }
        
        helper(preorder, inorder, prest + 1, prest + count, midst, mididx - 1, cur->left);
        helper(preorder, inorder, prest + 1 + count, preed, mididx + 1, mided, cur->right);
    }
    
    TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
        if (preorder.empty() || inorder.empty()) {
            return nullptr;
        }
        
        TreeNode *root = nullptr;
        helper(preorder, inorder, 0, preorder.size() - 1, 0, inorder.size() - 1, root);
        return root;
    }
};

OJ106代码:

class Solution {
public:
    void helper (const vector<int> &inorder, const vector<int> &postorder, int midst, int mided, int postst, int posted, TreeNode *&cur) {
        if (!(midst <= mided && postst <= posted && posted - postst == mided - midst && mided < inorder.size() && postst >= 0)) {
            return;
        }
        
        cur = new TreeNode(postorder[posted]);
        if (midst == mided && postst == posted) {
            return;
        }
        
        int count = 0, mididx;
        for (int i = mided; i >= midst; i--) {
            if (postorder[posted] == inorder[i]) {
                mididx = i;
                break;
            } else {
                ++count;
            }
        }
        
        helper(inorder, postorder, midst, mididx - 1, postst, posted - count - 1, cur->left);
        helper(inorder, postorder, mididx + 1, mided, posted - count, posted - 1, cur->right);
    }
    
    TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
        if (inorder.empty() || postorder.empty()) {
            return nullptr;
        }
        
        TreeNode *root = nullptr;
        helper(inorder, postorder, 0, inorder.size() - 1, 0, postorder.size() - 1, root);
        return root;
    }
};

    原文作者:AVL树
    原文地址: https://blog.csdn.net/u010246947/article/details/78239278
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞