0 数据结构
struct TreeNode {
int val;
struct TreeNode *left;
struct TreeNode *right;
TreeNode(int x) :
val(x), left(NULL), right(NULL) {
}1
};
0 初始化二叉树
【思路】前序初始化建树,保证每个节点都有左右节点,#表示为空
#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
using namespace std;
struct TreeNode{
int val;
struct TreeNode* left;
struct TreeNode* right;
TreeNode(int x){
val = x;
left = NULL;
right = NULL;
}
};
TreeNode* deserialize(string& s){
if(s.empty())
return NULL;
if(s[0] == '#'){
s = s.substr(2);
return NULL;
}
TreeNode* res = new TreeNode(atoi(s.c_str()));
s = s.substr(s.find_first_of(',') + 1);
res->left = deserialize(s);
res->right = deserialize(s);
return res;
}
void preorder(TreeNode* pNode){
if(pNode == NULL)
return;
cout << pNode->val << " ";
preorder(pNode->left);
preorder(pNode->right);
}
void inorder(TreeNode* pNode){
if(pNode == NULL)
return;
inorder(pNode->left);
cout << pNode->val << " ";
inorder(pNode->right);
}
void postorder(TreeNode* pNode){
if(pNode == NULL)
return;
postorder(pNode->left);
postorder(pNode->right);
cout << pNode->val << " ";
}
int main() {
string s = "1,2,#,4,6,#,#,#,3,5,#,#,#,"; //preorder,keep node has left & right util #
TreeNode* pHead = deserialize(s);
cout << "preorder : ";
preorder(pHead);
cout << endl;
cout << "inorder : ";
inorder(pHead);
cout << endl;
cout << "postorder : ";
postorder(pHead);
cout << endl;
return 0;
}
1 遍历
1.0 梳理
- 二叉树是有两个方向的单链表
- 非递归的实现,对于遍历任务,因为无法像数组/字符串那样直接通过下标索引取值,往往需要做两件事
- 记录当前结点(TreeNode* pNode)
- 所以需要辅助空间(栈stack 或者队列 queue)
- 对于递归的简单理解,以root为根节点xxx,再以root->left为根节点xxx,再以root->right为根节点xxx,考虑终止条件
- 指针为NULL判断,防止越界
1.1 前序遍历
前序遍历:根结点 —> 左子树 —> 右子树
【链接】
https://www.nowcoder.com/questionTerminal/501fb3ca49bb4474bf5fa87274e884b4
【题目描述】
求给定的二叉树的前序遍历。
【输出】1 2 4 6 7 8 3 5
1.1.1 递归版本
class Solution {
public:
void preorder(TreeNode* root, vector<int>& res){
if(root == NULL)
return;
res.push_back(root->val);
preorder(root->left, res);
preorder(root->right, res);
}
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
preorder(root, res);
return res;
}
};
1.1.2 非递归版本 | 栈
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode*> s; // 用来暂存节点的栈
TreeNode* pNode = root; // 新建一个游标节点为根节点
vector<int> res;
// 当遍历到最后一个节点的时候,无论它的左右子树都为空,并且栈也为空
// 所以,只要不同时满足这两点,都需要进入循环
while(!s.empty() || pNode != NULL){
// 若当前考查节点非空,则输出该节点的值
// 先序,需要一直往左走
while(pNode != NULL){
res.push_back(pNode->val);
s.push(pNode); // 为了之后能找到该节点的右子树,暂存该节点
pNode = pNode->left;
}
// 一直到左子树为空,则开始考虑右子树
// 如果栈已空,就不需要再考虑
// 弹出栈顶元素,将游标等于该节点的右子树
if(!s.empty()){
pNode = s.top();
s.pop();
pNode = pNode->right;
}
}
return res;
}
};
1.2 中序遍历
中序遍历:左子树—> 根结点 —> 右子树
【链接】
https://www.nowcoder.com/questionTerminal/1b25a41f25f241228abd7eb9b768ab9b
【题目描述】
给出一棵二叉树,返回这棵树的中序遍历
【输出】4 7 6 8 2 1 3 5
1.2.1 递归版本
class Solution {
public:
void inorder(vector<int>& res, TreeNode* root){
if(root == NULL)
return;
inorder(res, root->left);
res.push_back(root->val);
inorder(res, root->right);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> res;
inorder(res, root);
return res;
}
};
1.2.2 非递归版本 | 栈
与前序非递归代码类似,只是push_back位置略有不同
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> s;
vector<int> res;
TreeNode* pNode = root;
while(!s.empty() || pNode != NULL){
while(pNode != NULL){
s.push(pNode);
pNode = pNode->left;
}
if(!s.empty()){
pNode = s.top();
s.pop();
res.push_back(pNode->val);
pNode = pNode->right;
}
}
return res;
}
};
1.3 后序遍历
后序遍历:左子树 —> 右子树 —> 根结点
【链接】
https://www.nowcoder.com/questionTerminal/32af374b322342b68460e6fd2641dd1b
【题目描述】
求给定的二叉树的后序遍历。
【输出】7 8 6 4 2 5 3 1
1.3.1 递归版本
class Solution {
public:
void postorder(vector<int>& res, TreeNode* root){
if(root == NULL)
return;
postorder(res, root->left);
postorder(res, root->right);
res.push_back(root->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
postorder(res, root);
return res;
}
};
1.3.2 非递归版本 | 栈
【方案一】巧妙的方法:将前序 [根 -> 左 -> 右] 改为 [根 -> 右 -> 左],然后reverse,等于后序 [左 -> 右 -> 根]
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> s;
vector<int> res;
TreeNode* p = root;
while(!s.empty() || p != NULL){
while(p != NULL){
res.push_back(p->val);
s.push(p);
p = p->right;
}
if(!s.empty()){
p = s.top();
s.pop();
p = p->left;
}
}
reverse(res.begin(), res.end());
return res;
}
};
【方案二】
用栈做辅助空间,先从根往左一直入栈,直到为空,
然后判断栈顶元素的右孩子,
如果不为空且未被访问过,则从它开始重复左孩子入栈的过程;
否则说明此时栈顶为要访问的节点(因为左右孩子都是要么为空要么已访问过了),出栈然后访问即可,
接下来再判断栈顶元素的右孩子…
直到栈空
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> res;
stack<TreeNode*> s;
TreeNode* pNode = root; //pNode记录当前结点
TreeNode* pPre = NULL; //pPre记录上一个结点(访问过或者理解为输出过)
while(pNode != NULL || !s.empty()){
while(pNode != NULL){ //左孩子一直入栈,直到左孩子为空
s.push(pNode);
p = p->left;
}
if(!s.empty()){
pNode = s.top(); //这里不出栈,仅用于取栈顶元素
pNode = pNode->right;
if(pNode != NULL && pNode != pPre){ //如果栈顶元素的右孩子不为空,且未被访问过
s.push(pNode); //则右孩子进栈,然后重复左孩子一直进栈直到为空的过程
pNode = pNode->left;
}
else{
pPre = s.top(); //否则出栈,访问,r记录刚刚访问的节点
s.pop();
res.push_back(pPre->val);
pNode = NULL; //保证继续走if路径,向上回溯
}
}
}
return res;
}
};
1.4 层次遍历 | 队列
主要看非递归版本,可以将子问题串起来
1.4.1 打印成一行
【链接】
https://www.nowcoder.com/questionTerminal/7fe2212963db4790b57431d9ed259701
【题目描述】
从上往下打印出二叉树的每个节点,同层节点从左至右打印
class Solution {
public:
vector<int> PrintFromTopToBottom(TreeNode* root) {
vector<int> res;
if(root == NULL)
return res;
queue<TreeNode*> q;
q.push(root);
while(!q.empty()){
int low = 0, high = q.size();
while(low < high){ //保证按层xxx的关键
TreeNode* pNode = q.front();
q.pop();
res.push_back(pNode->val);
if(pNode->left)
q.push(pNode->left);
if(pNode->right)
q.push(pNode->right);
low++;
}
}
return res;
}
};
1.4.2 打印成多行
【链接】
https://www.nowcoder.com/questionTerminal/445c44d982d04483b04a54f298796288
【题目描述】
从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。
【输出】
1
2、3
4、5
6
7、8
【难点】
- 插入哪一个vector
- 有多少层事先也不知道
- 层间左、右子树遍历时如何保证写入同一个vector
1.4.2.1 非递归版本 | 队列
层次遍历;先把树一层结点一次放到队列里面
队列:,了解push、pop、front内置函数
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int> > vec;
if(pRoot == NULL)
return vec;
queue<TreeNode*> q; //队列
q.push(pRoot);
while(!q.empty())
{
int low = 0, high = q.size();
vector<int> path; //记录一层所有结点的值
while(low < high)
{
TreeNode *t = q.front();
q.pop(); //弹出队列头
path.push_back(t->val);
if(t->left)
q.push(t->left); //插入队列尾部
if(t->right)
q.push(t->right);
low++;
}
vec.push_back(path);
}
return vec;
}
};
1.4.2.2 递归版本 | 标记所在层(了解即可)
class Solution {
public:
void DFS(vector<vector<int>>& res, TreeNode* pRoot, int depth){
if(pRoot == NULL)
return;
//首次遍历到新的一层
if(depth > res.size()){ //妙,首次会增加1个size,后续层内遍历无需push_back
res.push_back(vector<int> ());
}
res[depth-1].push_back(pRoot->val); //res下标从0开始
DFS(res, pRoot->left, depth + 1);
DFS(res, pRoot->right, depth + 1);
}
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int>> res;
DFS(res, pRoot, 1); //depth从1开始遍历
return res;
}
};
1.4.3 之字形打印多行
【链接】
https://www.nowcoder.com/questionTerminal/91b69814117f4e8097390d107d2efbe0
https://www.nowcoder.com/questionTerminal/47e1687126fa461e8a3aff8632aa5559
【题目描述】
请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推
【思路】
与打印多行类似,只需在偶数层,将vector reverse一下就行
class Solution {
public:
vector<vector<int> > Print(TreeNode* pRoot) {
vector<vector<int>> res;
if(pRoot == NULL)
return res;
int level = 1; //记录第几层
queue<TreeNode*> q;
q.push(pRoot);
while(!q.empty()){
vector<int> path;
int low = 0, high = q.size();
while(low < high){
TreeNode* pNode = q.front();
q.pop();
path.push_back(pNode->val);
if(pNode->left)
q.push(pNode->left);
if(pNode->right)
q.push(pNode->right);
low++;
}
//与打印多行的差别
if(level%2 == 0)
reverse(path.begin(), path.end());
res.push_back(path);
level++;
}
return res;
}
};
1.4.4 从下往上打印多行
【链接】
https://www.nowcoder.com/questionTerminal/d8566e765c8142b78438c133822b5118
【题目描述】
给定一个二叉树,返回该二叉树由底层到顶层的层序遍历,(从左向右,从叶子节点到根节点,一层一层的遍历)
例如:
给定的二叉树是{3,9,20,#,#,15,7},
3↵ / ↵ 9 20↵ / ↵ 15 7
该二叉树由底层到顶层层序遍历的结果是
[↵ [15,7]↵ [9,20],↵ [3],↵]
【思路】
与从上往下打印多行类似,只需将最终二维数组reverse一下即可
class Solution {
public:
vector<vector<int> > levelOrderBottom(TreeNode* root) {
vector<vector<int> > res;
if(root == NULL)
return res;
queue<TreeNode*> q; //队列
q.push(root);
while(!q.empty())
{
int low = 0, high = q.size();
vector<int> path; //记录一层所有结点的值
while(low < high)
{
TreeNode *t = q.front();
q.pop(); //弹出队列头
path.push_back(t->val);
if(t->left)
q.push(t->left); //插入队列尾部
if(t->right)
q.push(t->right);
low++;
}
res.push_back(path);
}
//与从上向下打印的不同
reverse(res.begin(), res.end());
return res;
}
};
1.5 中序遍历下一个节点 | 数据结构不一样
【链接】
https://www.nowcoder.com/questionTerminal/9023a0c988684a53960365b889ceaf5e
【题目描述】
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
【数据结构】
struct TreeLinkNode {
int val;
struct TreeLinkNode *left;
struct TreeLinkNode *right;
struct TreeLinkNode *next; //指向父节点的指针
TreeLinkNode(int x) :val(x), left(NULL), right(NULL), next(NULL) {
}
};
【题解】
思路:首先知道中序遍历的规则是:左根右,然后作图
图中中序遍历结果为:D N H B I E J A F K C L G M
因此,下一个节点一共有三种情况
- 如果有右子树,则找右子树的最左节点(eg:D,B,E,A,C,G)
- 没右子树,则回退找父节点,第一个父节点左孩子是当前节点的父节点(eg:N,I,L,H,J,K,M)
- 退到了根节点仍没找到,则返回null(eg:M)
class Solution {
public:
TreeLinkNode* GetNext(TreeLinkNode* pNode)
{
if(pNode == NULL)
return NULL;
//如果有右子树,则找右子树的最左节点
if(pNode->right != NULL){
pNode = pNode->right;
while(pNode->left != NULL)
pNode = pNode->left;
return pNode;
}
//没右子树,则找父节点,第一个父节点左孩子是当前节点的父节点
while(pNode->next != NULL){
if(pNode->next->left == pNode)
return pNode->next;
pNode = pNode->next;
}
//退到了根节点仍没找到,则返回null
return NULL;
}
};
2 重建二叉树
遗留问题:如果序列中存在重复,怎么解决
2.1 前序+中序
【链接】
https://www.nowcoder.com/questionTerminal/8a19cbe657394eeaac2f6ea9b0f6fcf6
https://www.nowcoder.com/questionTerminal/0ee054a8767c4a6c96ddab65e08688f4
【题目描述】
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回
可以假设树中不存在重复的节点
【思路】
- 前序的第一个元素值是二叉树的根节点
- 中序找到根节点的位置,前面为左子树序列、后面为右子树序列
- 因为前序遍历序列中左子树长度 = 中序遍历序列左子树长度,所以根据根节点位置将前序与中序分裂为:左前序、左中序、右前序、右中序
- 根据左前序、左中序递归构造左子树;根据右前序、右中序递归构建右子树
class Solution {
public:
TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
int len = pre.size();
if(len == 0)
return NULL;
TreeNode* pHead = new TreeNode(pre[0]); //前序遍历的第一个值必然是根结点
//中序遍历中找到根节点所在的位置
int i;
int split_idx;
for(i = 0; i < len; i++){
if(vin[i] == pre[0]){
split_idx = i;
break;
}
}
vector<int> left_pre, right_pre, left_vin, right_vin;
//构建左子树的前序、中序遍历序列
for(i = 0; i < split_idx; i++){
left_pre.push_back(pre[i+1]); //注意前序遍历下标需+1,因为第一个值已经去掉
left_vin.push_back(vin[i]);
}
//构建右子树的前序、中序遍历序列
for(i = split_idx+1; i < len; i++){
right_pre.push_back(pre[i]);
right_vin.push_back(vin[i]);
}
pHead->left = reConstructBinaryTree(left_pre, left_vin);
pHead->right = reConstructBinaryTree(right_pre, right_vin);
return pHead;
}
};
2.2 中序+后序
【链接】
https://www.nowcoder.com/questionTerminal/b0d07d0edc7f495696aecd265d5ef1b9
【题目描述】
给出一棵树的中序遍历和后序遍历,请构造这颗二叉树
注意:
保证给出的树中不存在重复的节点
【思路】
与前序+中序类似
class Solution {
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
int len = inorder.size();
if(len == 0)
return NULL;
//后序最后一个元素为根节点
TreeNode* pHead = new TreeNode(postorder[len-1]);
//中序寻找根节点所在位置
int split_idx;
int i;
for(i = 0; i < len; i++){
if(inorder[i] == postorder[len-1]){
split_idx = i;
break;
}
}
//划分左中序、左后序、右中序、右后序
vector<int> left_in, right_in, left_post, right_post;
for(i = 0; i < split_idx; i++){
left_in.push_back(inorder[i]);
left_post.push_back(postorder[i]);
}
for(i = split_idx+1; i < len; i++){
right_in.push_back(inorder[i]);
right_post.push_back(postorder[i-1]);
}
//递归构建左右子树
pHead->left = buildTree(left_in, left_post);
pHead->right = buildTree(right_in, right_post);
return pHead;
}
};
2.3 层序(较难)
这一部分主要利用了层序遍历的树形结构性质以及队列这一数据结构
前提是完全二叉树
解题思路:由二叉树的性质可知,设编号为i的节点的父节点的编号为i/2,左孩子的编号为2i,右孩子的编号为2i+1;
Node* get_tree(vector<int> vec)
{
int vec_size = vec.size();
if(vec_size == 0)
return NULL;
Node* l;
Node* r;
Node* root;
Node* node;
bool has_left = false;
bool has_right = false;
queue<Node*> q;
for(int i = 0; i < vec_size; i++)
{
//左子树
if(2*i+1 < vec_size)
l = new Node(vec[2*i+1]);
else
l = NULL;
//右子树
if(2*i+2 < vec_size)
r = new Node(vec[2*i+2]);
else
r = NULL;
node = new Node(vec[i],l,r);
if(i == 0)
{
root = node;
}
else if(i%2 == 1)
{
Node* tmp = q.front();
tmp->left = node;
has_left = true;
}
else
{
Node* tmp = q.front();
tmp->right = node;
has_right = true;
}
//判断左右子树是否都建立联系
if(has_left && has_right)
{
has_left = false;
has_right = false;
q.pop();
}
q.push(node);
}
return root;
}
2.4 序列化 | 正反(好题)
【链接】
https://www.nowcoder.com/questionTerminal/cf7e25aa97c04cc1a68c8f040e71fb84
【题目描述】
请实现两个函数,分别用来序列化和反序列化二叉树
二叉树的序列化是指:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。
二叉树的反序列化是指:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。
例如,我们可以把一个只有根节点为1的二叉树序列化为"1,",然后通过自己的函数来解析回这个二叉树
【思路】
前序遍历,当节点为NULL时,用’#'填充,最终结构为完全二叉树
【注意点】
- string s如何转char*数组
- char *ret = new char[s.length() + 1];
- strcpy(ret, s.c_str());
- char* str数组如何转string
- string s(str);
- int val值如何转string
- tostring(val)
- string s如何转int
- stoi(s)
- string find_first_of的使用
class Solution {
public:
void serialize_core(TreeNode* pNode, string& s){
if (pNode == NULL){
s.push_back('#');
s.push_back(',');
return;
}
s += to_string(pNode->val);
s.push_back(',');
serialize_core(pNode->left, s);
serialize_core(pNode->right, s);
}
char* Serialize(TreeNode *root) {
if (root == NULL)
return NULL;
string s = "";
serialize_core(root, s);
char *ret = new char[s.length() + 1];
strcpy(ret, s.c_str()); //string s.c_str() 返回包含'\0'的char*数组
return ret;
}
TreeNode* deserialize_core(string &s)
{
if (s.empty())
return NULL;
if (s[0] == '#'){
s = s.substr(2); //#和, 占两个位置
return NULL;
}
TreeNode *ret = new TreeNode(stoi(s));
s = s.substr(s.find_first_of(',') + 1);
ret->left = deserialize_core(s);
ret->right = deserialize_core(s);
return ret;
}
TreeNode* Deserialize(char *str) {
if (str == NULL)
return NULL;
string s(str);
return deserialize_core(s);
}
};
3 二叉树深度
3.1 最大深度
【链接】
https://www.nowcoder.com/questionTerminal/435fb86331474282a3499955f0a41e8b
【题目描述】
输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。
class Solution {
public:
int TreeDepth(TreeNode* pRoot)
{
if(pRoot == NULL)
return 0;
return 1 + max(TreeDepth(pRoot->left), TreeDepth(pRoot->right));
}
};
3.2 判断是否平衡二叉树
【链接】
https://www.nowcoder.com/questionTerminal/8b3b95850edb4115918ecebdf1b4d222
【题目描述】
输入一棵二叉树,判断该二叉树是否是平衡二叉树。
平衡二叉树定义:每个节点的左右两个子树高度差的绝对值不超过1的二叉树
3.2.1 方案一:暴力递归 | 重复
最直接的做法,遍历每个结点,借助一个获取树深度的递归函数,根据该结点的左右子树高度差判断是否平衡,然后递归地对左右子树进行判断
每个节点都去求一次高度,存在重复
class Solution {
public:
int getDepth(TreeNode* pNode){
if(pNode == NULL)
return 0;
return 1 + max(getDepth(pNode->left), getDepth(pNode->right));
}
bool IsBalanced_Solution(TreeNode* pRoot) {
if(pRoot == NULL)
return true;
int leftDepth = getDepth(pRoot->left);
int rightDepth = getDepth(pRoot->right);
if(abs(leftDepth - rightDepth) > 1)
return false;
return IsBalanced_Solution(pRoot->left) && IsBalanced_Solution(pRoot->right);
}
};
3.2.2 方案二:只计算一次 | 剪枝
方案一有很明显的问题,在判断上层结点的时候,会多次重复遍历下层结点,增加了不必要的开销。
如果改为从下往上遍历,如果子树是平衡二叉树,则返回子树的高度;如果发现子树不是平衡二叉树,则直接停止遍历,这样至多只对每个结点访问一次
class Solution {
public:
int get_depth(TreeNode* pRoot){
if(pRoot == NULL)
return 0;
int left_depth = get_depth(pRoot->left);
if(left_depth == -1) //左子树不平衡,则停止
return -1;
int right_depth = get_depth(pRoot->right);
if(right_depth == -1) //右子树不平衡,则停止
return -1;
return abs(left_depth - right_depth) > 1 ? -1 : 1 + max(left_depth, right_depth);
}
bool IsBalanced_Solution(TreeNode* pRoot) {
return get_depth(pRoot) != -1; //-1表示不平衡
}
};
3.3 最小深度
【链接】
https://www.nowcoder.com/questionTerminal/e08819cfdeb34985a8de9c4e6562e724
【题目描述】
求给定二叉树的最小深度。最小深度是指树的根结点到最近叶子结点的最短路径上结点的数量。
示例1
输入
{1,2,3,4,5}
输出
2
【题解】递归
- 若为空树返回0;
- 若左子树为空,则返回右子树的最小深度+1;(加1是因为要加上根这一层,下同)
- 若右子树为空,则返回左子树的最小深度+1;
- 若左右子树均不为空,则取左、右子树最小深度的较小值,+1;
【举例】:如何类似最大深度方案,可能没到叶子节点就结束了
用例:
{1,2}
对应输出应该为:
2
你的输出为:
1
class Solution {
public:
int minDepth(TreeNode* root) {
if(root == NULL)
return 0;
if(root->left == NULL)
return 1 + minDepth(root->right);
if(root->right == NULL)
return 1 + minDepth(root->left);
return 1 + min(minDepth(root->left), minDepth(root->right));
}
};
4 求和
4.1 判断是否存在符合条件的路径和
【链接】
https://www.nowcoder.com/questionTerminal/508378c0823c423baa723ce448cbfd0c
【题目描述】
给定一个二叉树和一个值sum,判断是否有从根节点到叶子节点的节点值之和等于sum的路径,
例如:
给出如下的二叉树,sum=22,
5↵ / ↵
4 8↵ / / ↵
11 13 4↵ / / ↵
7 2 5 1
返回true,因为存在一条路径5->4->11->2的节点值之和为22
【题解】递归大法,注意1)引用 2)return 3)pNode->left、right是否为空判断
class Solution {
public:
void DFS(TreeNode* pNode, int cur, int sum, bool& res){
if(pNode == NULL)
return;
cur += pNode->val;
//当前结点为叶子结点,且路径和等于sum
if(pNode->left == NULL && pNode->right == NULL && cur == sum){
res = true;
return;
}
//遍历左子树
if(pNode->left)
DFS(pNode->left, cur, sum, res);
//遍历右子树
if(pNode->right)
DFS(pNode->right, cur, sum, res);
//回溯
cur -= pNode->val;
}
bool hasPathSum(TreeNode* root, int sum) {
bool res = false;
DFS(root, 0, sum, res);
return res;
}
};
4.2 输出所有符合条件的路径和
【链接】
https://www.nowcoder.com/questionTerminal/840dd2dc4fbd4b2199cd48f2dadf930a
【题目描述】
给定一个二叉树和一个值sum,请找出所有的根节点到叶子节点的节点值之和等于sum的路径,
例如:
给出如下的二叉树,sum=22,
5↵ / ↵
4 8↵ / / ↵
11 13 4↵ / / ↵
7 2 5 1
返回
[↵ [5,4,11,2],↵ [5,8,4,5]↵]↵
【题解】与上类似,输出为二维数组
class Solution {
public:
void DFS(TreeNode* pNode, vector<vector<int>>& res, vector<int> path, int cur, int sum){
if(pNode == NULL)
return;
cur += pNode->val;
path.push_back(pNode->val);
if(pNode->left == NULL && pNode->right == NULL && cur == sum){
res.push_back(path);
return;
}
if(pNode->left)
DFS(pNode->left, res, path, cur, sum);
if(pNode->right)
DFS(pNode->right, res, path, cur, sum);
cur -= pNode->val;
path.pop_back();
}
vector<vector<int> > pathSum(TreeNode* root, int sum) {
vector<vector<int>> res;
vector<int> path;
DFS(root, res, path, 0, sum);
return res;
}
};
4.3 根到叶子表示数字求和
【链接】
https://www.nowcoder.com/questionTerminal/185a87cd29eb42049132aed873273e83
【题目描述】
给定一个仅包含数字0-9的二叉树,每一条从根节点到叶子节点的路径都可以用一个数字表示。
例如根节点到叶子节点的一条路径是1->2->3,那么这条路径就用123来代替。
找出根节点到叶子节点的所有路径表示的数字之和
例如:
1↵ / ↵
2 3
根节点到叶子节点的路径1->2用数字12代替
根节点到叶子节点的路径1->3用数字13代替
所以答案为12+13=25
【题解】整体思路不变,递归和回溯的操作 改为 [数字*10+val] 与 [数字/10]
class Solution {
public:
void DFS(TreeNode* pNode, int& sum, int path_sum){
if(pNode == NULL)
return;
path_sum = path_sum * 10 + pNode->val;
if(pNode->left == NULL && pNode->right == NULL){
sum += path_sum;
return;
}
if(pNode->left)
DFS(pNode->left, sum, path_sum);
if(pNode->right)
DFS(pNode->right, sum, path_sum);
path_sum /= 10;
}
int sumNumbers(TreeNode* root) {
int path_sum = 0;
int sum = 0;
DFS(root, sum, path_sum);
return sum;
}
};
4.4 任意结点间最大路径和(好题)
【链接】
https://www.nowcoder.com/questionTerminal/da785ea0f64b442488c125b441a4ba4a
【题目描述】
给定一个二叉树,请计算节点值之和最大的路径的节点值之和是多少。
这个路径的开始节点和结束节点可以是二叉树中的任意节点
例如:
给出以下的二叉树,
1↵ / ↵
2 3
返回的结果为6
【思路】
首先我们分析一下对于指定某个节点为根时,最大的路径和有可能是哪些情况:
- 第一种是左子树的路径加上当前节点,
- 第二种是右子树的路径加上当前节点,
- 第三种是左右子树的路径加上当前节点(相当于一条横跨当前节点的路径),
- 第四种是只有自己的路径。
乍一看似乎以此为条件进行自下而上递归就行了,然而这四种情况只是用来计算以当前节点根的最大路径
如果当前节点上面还有节点,那它的父节点是不能累加第三种情况的
所以我们要计算两个最大值,一个是当前节点下最大路径和,另一个是如果要连接父节点时最大的路径和。我们用前者更新全局最大量,用后者返回递归值就行了
class Solution {
public:
//返回以pNode(当前节点)为根节点的最大路径和
int DFS(TreeNode* pNode, int& max_sum){
if(pNode == NULL)
return 0;
int cur_max_sum = pNode->val;
int left_max = DFS(pNode->left, max_sum);
int right_max = DFS(pNode->right, max_sum);
if(left_max > 0)
cur_max_sum += left_max;
if(right_max > 0)
cur_max_sum += right_max;
if(cur_max_sum > max_sum)
max_sum = cur_max_sum;
//连接当前结点与子树结点最大和时,只能选择左侧或者右侧,因为路径只能有一个方向
return pNode->val + max(max(left_max, right_max), 0);
}
int maxPathSum(TreeNode* root) {
int max_sum = INT_MIN; //记录整棵树任意节点之间的最大和
DFS(root, max_sum);
return max_sum;
}
};
5 二叉树结构
5.1 二叉树镜像
【链接】
https://www.nowcoder.com/questionTerminal/564f4c26aa584921bc75623e48ca3011
【题目描述】
操作给定的二叉树,将其变换为源二叉树的镜像。
输入/输出描述:
二叉树的镜像定义:源二叉树
8
/ \
6 10
/ \ /\
5 7 9 11
镜像二叉树
8
/ \
10 6
/ \ / \
11 9 7 5
class Solution {
public:
void mirrorCore(TreeNode* pNode){
if(pNode == NULL)
return;
if(pNode->left == NULL && pNode->right == NULL)
return;
TreeNode* tmp = pNode->left;
pNode->left = pNode->right;
pNode->right = tmp;
mirrorCore(pNode->left);
mirrorCore(pNode->right);
}
void Mirror(TreeNode *pRoot) {
TreeNode* pNode = pRoot;
mirrorCore(pNode);
}
};
5.2 判断是否对称树
【链接】
https://www.nowcoder.com/questionTerminal/1b0b7f371eae4204bc4a7570c84c2de1
【题目描述】
给定一棵二叉树,判断琪是否是自身的镜像(即:是否对称)
例如:下面这棵二叉树是对称的
1↵ / ↵
2 2↵ / / ↵
3 4 4 3↵
下面这棵二叉树不对称。
1↵ / ↵
2 2↵ ↵
3 3
5.2.1 递归版本
- 最朴素的想法是,先通过递归得到树的镜像树,然后与原树逐个判断是否相等
- (代码是这个版本)更巧妙的想法是,复制原树得到x、y两颗树,一个往左、一个往右遍历,判断是否相等(注意边界)
【思路拓展】
- 树对称还有一个性质:前序 [根 -> 左 -> 右] 与 [根 -> 右 -> 左] 遍历结果一样
class Solution {
public:
bool check(TreeNode* x, TreeNode* y){
if(x == NULL && y == NULL) //两个都为空
return true;
else if(x == NULL || y == NULL) //其中一个为空
return false;
else{ //两个都不为空
if(x->val == y->val)
return check(x->left, y->right) && check(x->right, y->left);
else
return false;
}
}
bool isSymmetric(TreeNode* root) {
return check(root, root);
}
};
5.2.2 非递归版本 | 队列 | 层次遍历
【思路 & 注意点】
- 与【层次遍历】打印数据,可以结合起来看
- 当节点为NULL时,可以插入到队列,保证size为偶数
- 从第3层(顶层为第1层)开始,注意插入队列顺序(从两边到中间,两个点的子节点顺序是:左右|右左)
class Solution {
public:
bool isSymmetric(TreeNode* root) {
if(root == NULL)
return true;
queue<TreeNode*> q;
q.push(root->left);
q.push(root->right);
bool res = true;
while(!q.empty()){
int size = q.size();
//判断当前队列中数据(一层)是否对称
while(size > 0){
//每次取前2个数据判断
TreeNode* left = q.front();
q.pop();
TreeNode* right = q.front();
q.pop();
size -= 2;
if(left == NULL && right == NULL) //两个节点都为空
continue;
else if(left == NULL || right == NULL) //只有一个节点为空
return false;
else{ //两个节点都不为空
if(left->val != right->val)
return false;
else{
//巧妙地将两边的数据,(从两边到中间)顺序放入队列中
q.push(left->left);
q.push(right->right);
q.push(left->right);
q.push(right->left);
}
}
}
}
return res;
}
};
5.3 判断B树是否为A树的子结构
【链接】
https://www.nowcoder.com/questionTerminal/6e196c44c7004d15b1610b9afca8bd88
【题目描述】
输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)
5.3.1 方案一:递归判断
两层递归
class Solution {
public:
bool DFS(TreeNode* pNode1, TreeNode* pNode2){
if(pNode2 == NULL) //如果Tree2已经遍历完了都能对应的上,返回true
return true;
else if(pNode1 == NULL) //如果Tree2还没有遍历完,Tree1却遍历完了。返回false
return false;
else{
if(pNode1->val != pNode2->val)
return false; //如果其中有一个点没有对应上,返回false
else //再分别看对应左子树、右子树情况
return DFS(pNode1->left, pNode2->left) && DFS(pNode1->right, pNode2->right);
}
}
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
bool res = false;
//当Tree1和Tree2都不为零的时候,才进行比较。否则直接返回false
if(pRoot1 != NULL && pRoot2 != NULL){
//如果找到了对应Tree2的根节点的点
if(pRoot1->val == pRoot2->val)
res = DFS(pRoot1, pRoot2);
//如果找不到,那么就再去root的左儿子当作起点,去判断时候包含Tree2
if(!res)
res = HasSubtree(pRoot1->left, pRoot2);
//如果还找不到,那么就再去root的右儿子当作起点,去判断时候包含Tree2
if(!res)
res = HasSubtree(pRoot1->right, pRoot2);
}
return res;
}
};
5.3.2 方案二:前序遍历+数组判断
效率相对低,仅供拓宽思路
class Solution {
public:
void preorder(TreeNode* pNode, vector<int>& vec){
if(pNode == NULL)
return;
vec.push_back(pNode->val);
preorder(pNode->left, vec);
preorder(pNode->right, vec);
}
bool HasSubtree(TreeNode* pRoot1, TreeNode* pRoot2)
{
vector<int> vec1, vec2;
preorder(pRoot1, vec1);
preorder(pRoot2, vec2);
if(vec2.size() == 0)
return false;
bool res = false;
for(int i = 0; i < vec1.size(); i++){
if(vec1[i] != vec2[0])
break;
else{
int left = i + 1;
int right = 1; //0+1
while(right < vec2.size()){
if(vec1[left] != vec2[right])
break;
left++;
right++;
}
if(right == vec2.size())
return true;
}
}
return res;
}
};
6 二叉搜索/查找树相关
6.1 数组[1,n]唯一二叉搜索树个数
【链接】
https://www.nowcoder.com/profile/7285165/codeBookDetail?submissionId=17378659
【题目描述】
给定一个值n,能构建出多少不同的值包含1…n的二叉搜索树(BST)?
例如
给定 n = 3, 有五种不同的二叉搜索树(BST)
1 3 3 2 1
\ / / / \ \
3 2 1 1 3 2
/ / \ \
2 1 2 3
【题解】
- 求解数组[1,i] (即节点数为i)的唯一BST个数,等价于分别计算以1到i为根节点的BST个数,再求和
- 以j为根节点的BST个数,等于左子树BST个数 乘以 右子树BST个数(乘法原理),其中[左子树BST个数]等价于[节点数为左子树个数的BST个数]
- 根据BST性质,左子树为[1,i-1],右子树为[i+1,n]
- BST个数与具体值无关(比如:[1,2,3]与[1,2,4]的唯一BST个数相等)
【递归法】
为了与下面动规对应,i表示节点数,j表根节点为i
class Solution {
public:
int numTrees(int i) {
if(i <= 1)
return 1;
int uniqueBST = 0;
for(int j = 1; j <= i; j++){
uniqueBST += numTrees(j-1)*numTrees(i-j);
}
return uniqueBST;
}
};
【动态规划法】
解决递归法中重复计算、时间复杂度高的问题
dp[i]表示节点数为i的唯一BST个数(从小到大,遍历存储,不存在重复计算)
class Solution {
public:
int numTrees(int n) {
if (n <= 0)
return 0;
int* dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for (int i = 2; i <= n; i++) { //i表示节点数
for (int j = 1; j <= i; j++) { //以j为根,j的取值范围在[1,i]
dp[i] += dp[j - 1] * dp[i - j];
}
}
return dp[n];
}
};
6.2 全部数组[1,n]二叉搜索树
【链接】
https://www.nowcoder.com/questionTerminal/98aaaefacaca44b9b4f2f2bd75780664
【题目描述】
给定一个值n,请生成所有的存储值1…n.的二叉搜索树(BST)的结构
【题解】
6.1输出个数,6.1输出每个BST的头结点,思想类似。
具体实现较为巧妙,递归返回的是一个TreeNode*的数组
https://developer.aliyun.com/article/3692
class Solution {
public:
vector<TreeNode*> generate(int start,int end){
vector<TreeNode*> subTree;
if(start > end){
subTree.push_back(NULL);
return subTree;
}
// i作为根节点
for(int i = start;i <= end;i++){
// 以i为根节点的树,其左子树由[start,i-1]构成,其右子树由[i+1,end]构成。
// 返回的是不同二叉查找树的根节点,几种二叉查找树就返回几个根节点
vector<TreeNode*> leftSubTree = generate(start,i-1);
vector<TreeNode*> rightSubTree = generate(i+1,end);
// 左子树右子树跟根节点连接
// 以i为根的树的个数,等于左子树的个数乘以右子树的个数
for(int j = 0;j < leftSubTree.size();j++){
for(int k = 0;k < rightSubTree.size();k++){
TreeNode* node = new TreeNode(i);
node->left = leftSubTree[j];
node->right = rightSubTree[k];
subTree.push_back(node);
}
}
}
return subTree;
}
vector<TreeNode*> generateTrees(int n) {
if(n == 0)
return generate(1,0);
return generate(1, n);
}
};
6.3 判断二叉搜索树
【链接】
https://www.nowcoder.com/questionTerminal/fd7f880072914464a13b89af242c0ce5
【题目描述】
判断给出的二叉树是否是一个二叉搜索树(BST)
二叉搜索树的定义如下
- 一个节点的左子树上节点的值都小于自身的节点值
- 一个节点的右子树上节点的值都大于自身的节点值
- 所有节点的左右子树都必须是二叉搜索树
【题解】
递归判断
先看根节点,再看左、右节点
可以结合[判断二叉平衡树]一起理解
class Solution {
public:
bool isValidBST(TreeNode* root) {
if(root == NULL)
return true;
if(root->left){
if(root->left->val >= root->val)
return false;
else
return isValidBST(root->left);
}
if(root->right){
if(root->right->val <= root->val)
return false;
else
return isValidBST(root->right);
}
//左右子树为空
return true;
}
};
6.4 升序数组转平衡BST
注:平衡BST不唯一,详见6.5【有趣的QA】部分
【链接】
https://www.nowcoder.com/questionTerminal/7e5b00f94b254da599a9472fe5ab283d
【题目描述】
给出一个升序排序的数组,将其转化为平衡二叉搜索树(BST).
示例1
输入
[-1,0,1,2]
输出
{1,0,2,-1}
【题解】
- 平衡BST,因此从数组中间开始构件树
- 递归构建、二分查找
class Solution {
public:
TreeNode* toBST(vector<int>& num, int low, int high){
if(low > high)
return NULL;
int len = high - low + 1;
int mid;
if(len % 2 == 0) //序列共偶数个值
mid = low + (high - low) / 2 + 1;
else
mid = low + (high - low) / 2;
TreeNode* root = new TreeNode(num[mid]);
root -> left = toBST(num, low, mid - 1);
root -> right = toBST(num, mid + 1, high);
return root;
}
TreeNode* sortedArrayToBST(vector<int>& num) {
if(num.size() == 0)
return NULL;
return toBST(num, 0, num.size()-1);
}
};
6.5 升序链表转平衡BST
注:平衡BST不唯一,详见6.5【有趣的QA】部分
【链接】
https://www.nowcoder.com/questionTerminal/86343165c18a4069ab0ab30c32b1afd0
【题目描述】
给定一个单链表,其中的元素按升序排序,请将它转化成平衡二叉搜索树(BST)
示例1
输入
{-1,0,1,2}
输出
{1,0,2,-1}
【思路】
这道题是要求把有序链表转为二叉搜索树,和之前那道Convert Sorted Array to Binary Search Tree思路完全一样,只不过是操作的数据类型有所差别,一个是数组,一个是链表。数组方便就方便在可以通过index直接访问任意一个元素,而链表不行。由于二分查找法每次需要找到中点,而链表的查找中间点可以通过快慢指针来操作。找到中点后,要以中点的值建立一个数的根节点,然后需要把原链表断开,分为前后两个链表,都不能包含原中节点,然后再分别对这两个链表递归调用原函数,分别连上左右子节点即可。
【有趣的QA】
Q:你好,请问为什么只能用fast && fast.next,当我用fast->next && fast->next->next就有错
A:实际上你这么做也是对的,因为平衡二叉树本就不唯一,但是判题程序中答案只有一个,所以显示不通过。具体错在哪里呢?比如1->2->3->4->null这个链表,答主的写法认为3是中间节点,你的写法认为2是中间节点,所以得到的答案不一致,而实际上2和3都可以作为中间节点。对于链表长度为奇数的情况,得到的结果是一致的。
class Solution {
public:
TreeNode* toBST(ListNode* head, ListNode* tail){
if(head == tail)
return NULL;
//申请两个指针,fast移动速度是low的两倍
ListNode* fast = head;
ListNode* slow = head;
while(fast != tail && fast->next != tail){
fast = fast->next->next;
slow = slow->next;
}
TreeNode* root = new TreeNode(slow->val);
root->left = toBST(head, slow);
root->right = toBST(slow->next, tail);
return root;
}
TreeNode* sortedListToBST(ListNode* head) {
return toBST(head, NULL);
}
};
6.6 判断数组是否为BST后序遍历结果
【链接】
https://www.nowcoder.com/questionTerminal/a861533d45854474ac791d90e447bafd
【题目描述】
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。
【题解】
BST的后序序列的合法序列是,对于一个序列S,最后一个元素是x (也就是根),如果去掉最后一个元素的序列为T,那么T满足:T可以分成两段,前一段(左子树)小于x,后一段(右子树)大于x,且这两段(子树)都是合法的后序序列。完美的递归定义 : )
class Solution {
public:
bool check(vector<int>& seq, int low ,int high){
if(low >= high) //注意是大于等于,因为最终return中idx-1(即high)可能小于low
return true;
int idx;
for(idx = low; idx < high; idx++){ //重复定义 int idx,会内存超限:您的程序使用了超过限制的内存
if(seq[idx] > seq[high])
break;
}
for(int i = idx; i < high; i++)
if(seq[i] < seq[high])
return false;
return check(seq, low, idx - 1) && check(seq, idx, high-1);
}
bool VerifySquenceOfBST(vector<int> sequence) {
if(sequence.size() == 0)
return false;
return check(sequence, 0, sequence.size()-1);
}
};
6.7 二叉搜索树第k小结点
【链接】
https://www.nowcoder.com/questionTerminal/ef068f602dde4d28aab2b210e859150a
【题目描述】
给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。
【题解】
BST + 中序遍历为递增有序数组;在第k个值的时候赋值即可
class Solution {
public:
TreeNode* KthNode(TreeNode* pRoot, int k){
if(pRoot == NULL || k == 0)
return NULL;
stack<TreeNode*> s;
TreeNode* p = pRoot;
TreeNode* res = NULL;
while(!s.empty() || p != NULL){
while(p != NULL){
s.push(p);
p = p->left;
}
if(!s.empty()){
p = s.top();
s.pop();
//类似之前push_back之类操作
k--;
if(k == 0){
res = p;
break;
}
p = p->right;
}
}
return res;
}
};
6.8 BST转双向排序链表 | 中序遍历
【链接】
https://www.nowcoder.com/questionTerminal/947f6eb80d944a84850b0538bf0ec3a5
【题目描述】
输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
【题解】
中序遍历完成时,前一个结点与当前节点互指;保持更新前一个节点值
class Solution {
public:
void inorder(TreeNode* pNode, TreeNode*& pPre){ //pPre一定要引用,因为后面pPre的改变要返回去给前一次的调用
if(pNode == NULL)
return;
if(pNode->left)
inorder(pNode->left, pPre);
//中序遍历对节点的访问顺序和转换完链表从左到右的顺序是一样的
//所以在中序遍历时完成相邻两个节点的互指即可
pNode->left = pPre;
if(pPre)
pPre->right = pNode;
pPre = pNode;
if(pNode->right)
inorder(pNode->right, pPre);
}
TreeNode* Convert(TreeNode* pRootOfTree){
if(pRootOfTree == NULL)
return NULL;
TreeNode* pPre = NULL;
inorder(pRootOfTree, pPre);
TreeNode* res = pRootOfTree;
while(res ->left)
res = res ->left;
return res;
}
};