leetcode之二叉树类之二叉树深度系列-----104/111/110/108/109 二叉树最大/最小深度/AVL树的判断和由有序序列生成(牵扯分治相关,OJ105/106)
2017-10-15 11:14
826 查看
二叉树的最大、最小深度,首先明确一点,不论最大还是最小,二叉树的深度都是基于leaf节点的,即二叉树的leaf节点的层数可以称之为深度,而求最大深度或最小深度,就是找深度最大是多少,最小是多少,前提必须是合法的深度。
同样是到leaf才见分晓的判断,走先序遍历,到达leaf就返回深度,而求最大或最小深度,就是返回左深度和右深度的max或min
利用极端例子思考,一个二叉树,root的左子树有很多层节点,右子树只有一个节点,那么最大深度是左子树到最深叶子的层数,最小深度就是2
如果root只有一侧有节点,如左或右某一侧为空,那么最小深度为2,最大深度依然需要计算
如果root没有任何子节点,那么最大深度和最小深度都是1
OJ104代码:
OJ111代码:
OJ110,判断一个搜索二叉树是否为AVL树,AVL树的定义是,任意一个节点的左子树和右子树的深度差别不能超过1,所以需要对每一个节点,求出左子树深度和右子树深度,典型需要后序遍历,然后判断是否出现不符合要求case
递归函数的返回值需要用于返回树的深度,所以可以传一个引用来保存不符合要求的时候。这是二叉树题的典型方式。
OJ110代码:
OJ108和OJ109都是给定一个有序序列,然后生成AVL树,方法都显然是以该序列的不断的二分,生成AVL树一层层的节点,差异是OJ108是数组,可以直接取索引定位mid节点及左右范围,而OJ109是链表,总需要通过快慢指针找mid节点;这两道题考察重点为分治;
注意有些题的题意与OJ108不同,但也是基于分治,如"由先序遍历序列和中序遍历序列,或由后序遍历序列和中序遍历序列,生成二叉树"(OJ105/106);而OJ109在考察AVL树原理之外,更多考察的是单链表的快慢指针找中点和边界处理细节,OJ109比较容易做错,在实战中可以作为一道比较恶心的题出现;
OJ108,给定有序数组生成AVL树,不断二分找mid节点生成每一层的,进而用左半部分生成左子树,右半部分生成右子树
OJ108代码:
OJ109,和OJ108唯一区别是由有序数组变为有序链表,麻烦点在于找中点更麻烦
我的可以AC的方法是,用一个函数做链表的二分,同时将原先链表分裂为两个新链表,即mid左边节点如果存在的话,不再指向mid而是指向nullptr,这里要注意mid左边没有节点的时候,head其实是和mid是一个节点,如果不做判断mid和head是否相同就继续处理,会出现重复不停的生成左子树的节点
程序总体结构和OJ108是差不多的,唯一就是多了找中点部分
OJ109代码:
OJ105和OJ106,由先序+中序,或由后序+中序,生成对应的二叉树,这两个题考察点和OJ108/OJ109不同,它们更侧重二叉树的深度遍历,但解题办法很相似,在这一并记录一下;
核心思路是:
1、由中序遍历自己无法还原对应的二叉树,因为中序遍历反映不了左子树右子树的顺序,如中序遍历123,可以是
但也可以是:
到底是哪个,这就需要先序或后序遍历,比如对于第1个图,先序遍历是123,后序遍历是321,对于第2个图先序遍历是132,后序遍历是321
2、先序遍历或后序遍历怎么样和中序遍历一起生成对应的二叉树
方法:临场在纸上画就行,如随意画下图:
先序遍历是321546,中序遍历是123456,后序遍历是124653
同时看先序+中序和后序+中序,步骤:
1、对于先序+中序,第0个节点是root,对于后序+中序,最后一个节点是root
2、生成了root后,中序遍历中,root节点的左半部分就是左子树,右半部分是右子树,对于图中例子就是,12是左子树,456是右子树
对于左子树12,先序中第0个节点是2,则2是左子树12的头节点,而在后序中最后一个节点是2,2是头节点
对于右子树456,同理,4是头节点
如果已经看了前面OJ108/OJ109的解法,看到这里应该知道程序大概怎么写,可见这道题同样属于分治,具体的,不论是先序+中序,还是后序+中序,每次定位到头节点并生成当前节点后,定位先序或后序的左子树、右子树部分,具体来说就是定位左子树、右子树部分,在数组的首尾索引,不断继续递归生成节点,递归停止条件就是左子树、右子树部分为空
OJ105代码:
OJ106代码:
同样是到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; } };
相关文章推荐
- 每天一道LeetCode-----计算二叉树的最大深度及最小深度,判断二叉树是否是高度平衡二叉树
- leetcode 111-二叉树最小深度
- Leetcode 95. Unique Binary Search Trees II及二叉树最大最小深度镜像树总结
- [Java代码] [Leetcode] Maximum and Minimum Depth of Binary Tree 二叉树的最小最大深度
- 【LeetCode101-110】二叉树对称及存储,前序中序遍历生成二叉树,中序后序生成二叉树,数组转化为AVL平衡树,判断二叉树是否平衡
- 【LeetCode-面试算法经典-Java实现】【104-Maximum Depth of Binary Tree(二叉树的最大深度)】
- 【LeetCode-面试算法经典-Java实现】【104-Maximum Depth of Binary Tree(二叉树的最大深度)】
- [LeetCode系列] 二叉树最大深度求解问题(C++递归解法)
- LeetCode:104_Maximum Depth of Binary Tree | 二叉树的最大深度 | 深度优先与广度优先深入理解
- [LeetCode 111] - 二叉树的最小深度 (Minimum Depth of Binary Tree)
- LeetCode—二叉树的最大最小深度
- [LeetCode-111] Minimum Depth of Binary Tree (二叉树最小深度)
- LeetCode:104_Maximum Depth of Binary Tree | 二叉树的最大深度 | Easy
- Leetcode 104 Maximum Depth of Binary Tree 二叉树的最大深度
- 【LeetCode-面试算法经典-Java实现】【111-Minimum Depth of Binary Tree(二叉树的最小深度)】
- LeetCode 104 Maximum Depth of Binary Tree(二叉树的最大深度)
- [LeetCode-104] Maximum Depth of Binary Tree(二叉树最大深度)
- LeetCode:104_Maximum Depth of Binary Tree | 二叉树的最大深度 | Easy
- LeetCode:111_Minimum Depth of Binary Tree | 二叉树的最小深度 | Easy
- leetcode 104求二叉树的最大深度