二叉树的最大、最小深度,首先明确一点,不论最大还是最小,二叉树的深度都是基于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;
}
};