C++中操作树节点时应该注意:
- 一般来说,树节点是由结构体表示的,如
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
如果要新建一个结构体指针,应该将指针初始化为NULL
TreeLinkNode *t=NULL;
或指定一个新的结构体变量:
TreeNode *t=new TreeNode(a);
注意到结构体可能有构造函数,此时要为新的结构体变量指定初始值。
举一反三
如果要求处理一棵二叉树的遍历序列,可以先找到二叉树的根结点,再基于根结点把整棵树的遍历序列拆分成左子树和右子树对应的子序列,接下来再递归地处理这两个子序列。
104. 求二叉树的最大深度
思路:
递归
int maxDepth(TreeNode* root) {
if (root == NULL) return 0;
return max(maxDepth(root->left), maxDepth(root->right)) + 1;
}
111. 求二叉树的最小深度
思路:
最小深度是指根节点到最近叶子节点的距离。
在修改上一题的基础上,还要考虑如果左右子树为空的情况。例如,如果左子树为空,就应该直接返回minDepth(root->right)+1
,而不能再是返回min(minDepth(root->left),minDepth(root->right))+1
,因为那样就是直接返回1了,而与最小深度的定义不同。
class Solution {
public:
int minDepth(TreeNode* root) {
if(!root) return 0;
if(!root->left) return minDepth(root->right)+1;
else if(!root->right) return minDepth(root->left)+1;
return min(minDepth(root->left),minDepth(root->right))+1;
}
};
110. 判断二叉树是否平衡
思路:
二叉树平衡的条件是:从根节点开始,如果左右子节点的深度相差大于1,则一定不平衡;否则,还要继续探究左右子节点是否平衡,直到叶子节点为止。
class Solution {
public:
bool isBalanced(TreeNode* root) {
if(root==NULL) return true;
if(abs(recursion(root->left)-recursion(root->right))>1) return false;
return (isBalanced(root->left) && isBalanced(root->right));
}
int recursion(TreeNode *root)
{
if(root==NULL) return 0;
return max(recursion(root->left),recursion(root->right))+1;
}
};
144. 前序遍历二叉树
思路一:
递归
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
recursion(root);
return result;
}
vector<int> result;//存放遍历结果
void recursion(TreeNode* root) {
if(!root) return;
result.push_back(root->val);// 1
recursion(root->left);// 2
recursion(root->right);// 3
}
};
注:前序、中序、后序的程序差不多,只是要将(1) (2) (3)处的顺序改变一下。
思路二:
非递归,利用栈。先取当前节点的值,再将右节点压入栈,然后将左节点压入栈。程序结束的条件是:栈为空。
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> m;
if(!root) return m;
stack<TreeNode *> s;
s.push(root);
while(!s.empty()){
TreeNode* n=s.top();
s.pop();
m.push_back(n->val);
if(n->right) s.push(n->right);
if(n->left) s.push(n->left);
}
return m;
}
94. 中序遍历二叉树
思路一:
递归方法见144
思路二:
非递归,要比前序遍历复杂一些:先要找到当前节点最左的子节点,在此过程中不断压栈。
找到最左节点后,再取中间节点,然后遍历右子树。
重复上述过程,直到栈为空以及到达最后的叶子节点。
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> v;
if(!root) return v;
stack<TreeNode *> s;
TreeNode *node=root;
while(node!=NULL)
{
s.push(node);
node = node->left;
}
while(!s.empty()) {
node = s.top();
s.pop();
v.push_back(node->val);
node = node->right;
while(node != NULL){//若节点非空,则把节点压入栈中,并遍历左子树
s.push(node);
node = node->left;
}
}
return v;
}
};
173. 用类实现中序遍历二叉树的迭代器
思路:
和上面的中序遍历BST类似,但把程序分成了三部分:
在构造函数BSTIterator(TreeNode *root)中,实现初始化,即定位到BST的最左边的节点,并把沿路的节点压入栈。
hasNext()函数根据栈stk是否为空,判断是否还有下一个元素。
next()函数实现:先将stk中最上的元素弹出,返回其值,然后开始遍历其右子树。
class BSTIterator {
public:
stack<TreeNode*> stk;
BSTIterator(TreeNode *root) {
while(!stk.empty())
stk.pop();
while(root){
stk.push(root);
root = root->left;
}
}
bool hasNext() {
return !stk.empty();
}
int next() {
TreeNode* tmp = stk.top();
stk.pop();
int res = tmp->val;
tmp = tmp->right;
while(tmp)
{
stk.push(tmp);
tmp = tmp->left;
}
return res;
}
};
/**
* Your BSTIterator will be called like this:
* BSTIterator i = BSTIterator(root);
* while (i.hasNext()) cout << i.next();
*/
145. 后序遍历二叉树
思路一:
递归方法见144
思路二:
非递归,比前序和中序遍历都要复杂。关键在于,当前节点能访问的条件是:
该节点是叶子节点,或者其左右子节点都已经访问过了。否则,就把它的右节点和左节点压入栈中。
class Solution {
public:
vector<int> postorderTraversal(TreeNode *root) {
vector<int> m;
if(!root) return m;
TreeNode* cur = NULL; //记录当前节点
TreeNode* pre = NULL;//记录上次访问的节点
stack<TreeNode*> s;
s.push(root);
while(!s.empty())
{
cur = s.top();
//当前节点能访问的前提,是它是叶子节点,或者左右子节点已经访问过了
if((!cur->left && !cur->right) ||
(pre && (cur->left == pre || cur->right == pre)))
{
m.push_back(cur->val);
s.pop();
pre = cur;
}
//否则,正常把右、左子树压栈
else
{
if(cur->right != NULL)
s.push(cur->right);
if(cur->left != NULL)
s.push(cur->left);
}
}
return m;
}
};
105 已知前序中序,求后序
Java版:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
if(inorder.length==0) return null;//空树
TreeNode root = dfs(preorder,inorder,0,preorder.length-1,0,inorder.length-1);
return root;
}
TreeNode dfs(int[] preorder, int[] inorder,int preL,int preR,int inL,int inR) {
if(preL>preR) return null;
TreeNode root=null;
int index;
for(index=inL;index<=inR;index++)
if(inorder[index]==preorder[preL])
break;
root = new TreeNode(inorder[index]);
root.left=dfs(preorder,inorder,preL+1,preL+1+index-1-inL,inL,index-1);
root.right=dfs(preorder,inorder,preL+1+index-inL,preR,index+1,inR);
return root;
}
}
string pre;
string in;
struct node{
char val;
node *left;
node *right;
};
node* construct(int pre_l, int pre_r, int in_l, int in_r)
{
if (pre_r < pre_l) return NULL;
//在in中确定根节点的位置
int index;
for (index = in_l; index <= in_r; index++) {
if (in[index] == pre[pre_l])
break;
}
node *root = new node;
root->val = pre[pre_l];//根节点的值是pre中范围[pre_l,pre_r]的第一个元素
root->left = construct(pre_l+1, pre_l+(index-in_l), in_l, index-1);//递归构造左子树
root->right = construct(pre_l+(index-in_l)+1, pre_r, index+1, in_r);//递归构造右子树
return root;
}
void post_Order(node *root)
{
if(root != NULL) {
post_Order(root->left);
post_Order(root->right);
cout<<(root->val);
}
}
int main() {
while(cin>>pre) {
cin>>in;
node *root = construct(0,pre.size()-1,0,in.size()-1);//构建出原来的树
post_Order(root);//输出后序遍历结果
}
return 0;
}
106. 根据中序和后序构建二叉树(Java)
思路:
中序的特点:某节点之前的节点都是它的左孩子节点,之后的节点都是它的右孩子节点。
后序的特点:给定一棵子树的所有节点集合P,从后往前遍历后序序列,首先出现的P中的某个节点,就是这棵子树的根节点。例如,后序序列的最后一个节点,是整棵树的根节点。
因此,每次结合中序和后序,找到当前范围的根节点,然后递归获得该根节点的左右子树。过程中,要用到inL和inR这两个表示中序序列范围的辅助变量。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Solution {
public TreeNode buildTree(int[] inorder, int[] postorder) {
if(inorder.length==0) return null;//空树
TreeNode root = dfs(inorder,postorder,0,inorder.length-1,0,postorder.length-1);
return root;
}
TreeNode dfs(int[] inorder, int[] postorder,int inL,int inR,int postL,int postR) {
if(postL>postR) return null;
TreeNode root=null;
int index;
for(index=inL;index<=inR;index++)
if(inorder[index]==postorder[postR])
break;
root = new TreeNode(inorder[index]);
root.left=dfs(inorder,postorder,inL,index-1,postL,postL+(index-inL)-1);
root.right=dfs(inorder,postorder,index+1,inR,postL+(index-inL),postR-1);
return root;
}
}
已知前序、后序求中序
我们知道,如果已知一棵树的前序和后序,那么可能无法唯一确定这棵树的后序遍历结果。因为前序的第1个节点、后序的最后1个节点是当前树的根节点,但如果前序的第2个节点等于后序的倒数第2个节点,那么除了根节点,剩下的节点可能是全来自于左子树,也可能全来自于右子树,也就是不唯一。
#include<cstdio>
#include<iostream>
#include <vector>
#include <string>
#include<cstring>
#include<stack>
#include<queue>
#include<set>
#include<map>
#include<unordered_map>
#include <numeric>
#include<limits.h>
#include <algorithm>
using namespace std;
typedef long long ll;
const int maxn = 1e3+3;
struct TreeNode{
int val;
TreeNode *left;
TreeNode *right;
TreeNode():val(0),left(NULL),right(NULL){};
};
vector<int> result;
TreeNode * root = NULL;
bool flag = true;
//中序遍历一棵树
void inorder(TreeNode * root)
{
if(root==NULL) return;
inorder(root->left);
result.push_back(root->val);
inorder(root->right);
}
//根据前序和后序建树,可能不唯一。如果发现不唯一,按照左子树建树。
TreeNode* build(vector<int> &pre,vector<int> &post,int preLeft,int preRight,int postLeft,int postRight)
{
TreeNode *root = NULL;
if(preLeft>preRight)
return root;
root = new TreeNode();
root->val = pre[preLeft];
if(preLeft==preRight)
return root;
int rightRoot = post[postRight-1];
int leftRoot = pre[preLeft+1];
//发现树不唯一
if(rightRoot == leftRoot)
{
flag=0;
root->left = build(pre,post,preLeft+1,preRight,postLeft,postRight-1);
return root;
}
//在前序序列中,左子树的右边界
int index1;
for(index1=preLeft+1;index1<preRight;index1++)
{
if(pre[index1+1]==rightRoot)
break;
}
//在后序序列中,右子树的左边界
int index2;
for(index2=postRight-1;index2>=postLeft;index2--)
{
if(post[index2-1]==leftRoot)
break;
}
root->left = build(pre,post,preLeft+1,index1,postLeft,index2-1);
root->right = build(pre,post,index1+1,preRight,index2,postRight-1);
return root;
}
int main()
{
int n;
cin>>n;
vector<int> pre(n+1,0);
vector<int> post(n+1,0);
for(int i=0;i<n;i++)
cin>>pre[i];
for(int i=0;i<n;i++)
cin>>post[i];
root = build(pre,post,0,n-1,0,n-1);
inorder(root);
//判断是否唯一
if(flag)
cout<<"Yes"<<endl;
else
cout<<"No"<<endl;
for(int i=0;i<result.size();i++)
{
cout<<result[i];
if(i<result.size()-1)
cout<<' ';
else
cout<<endl;
}
return 0;
}
二叉树中序遍历的下一个节点
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。
注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
思路:
分为以下几种情况:
- 二叉树为空,则返回空;
- 不管该节点是否是根节点,如果它有右孩子,则设置一个指针从该节点的右孩子出发,一直沿着指向左子结点的指针找到的叶子节点即为下一个节点;
- 如果节点没有右孩子,而且它是根节点,就直接返回null;
- 节点不是根节点,如果该节点是其父节点的左孩子,则返回父节点;否则继续向上遍历其父节点的父节点,直到某个节点是其父节点的左孩子,就返回父节点。如果访问到根节点都没有找到,就返回null。
public TreeLinkNode GetNext(TreeLinkNode node)
{
if(node == null)
return null;
//如果它有右孩子节点
if(node.right!=null)
{
TreeLinkNode temp=node.right;
while(temp.left!=null)
{
temp=temp.left;
}
return temp;
}
//如果节点没有右孩子,而且它是根节点,就直接返回null
else if(node.next == null)
return null;
//如果它是父节点的左孩子,直接返回父节点
if(node == node.next.left)
return node.next;
//一直向上找,直到某结点是该父节点的左节点
while(node.next!=null && node == node.next.right)
node=node.next;
//如果找到某结点,它是该父节点的左节点
if(node.next!=null)
return node.next;
//如果始终找不到,说明所有节点都被访问过了,返回null
else
return null;
}
二叉树中序遍历的上一个节点
给定一个二叉树和其中的一个结点,请找出中序遍历顺序的上一个结点并且返回。
注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。
思路:
和上一题是镜像问题。
类似的,分为以下几种情况:
1. 二叉树为空,则返回空;
2. 不管该节点是否是根节点,如果它有左孩子,则设置一个指针从该节点的左孩子出发,一直沿着指向右子结点的指针找到的叶子节点即为上一个节点;
3. 如果节点没有左孩子,而且它是根节点,就直接返回null;
4. 节点不是根节点,如果该节点是其父节点的右孩子,则返回父节点;否则继续向上遍历其父节点的父节点,直到某个节点是其父节点的右孩子,就返回其父节点。如果访问到根节点都没有找到,就返回null。
public TreeLinkNode GetNext(TreeLinkNode node)
{
if(node == null)
return null;
//如果它有左孩子节点
if(node.left!=null)
{
TreeLinkNode temp=node.left;
while(temp.right!=null)
{
temp=temp.right;
}
return temp;
}
//如果节点没有左孩子,而且它是根节点,就直接返回null
else if(node.next == null)
return null;
//如果它是父节点的右孩子,直接返回父节点
if(node == node.next.right)
return node.next;
//一直向上找,直到某结点是该父节点的右节点
while(node.next!=null && node == node.next.left)
node=node.next;
//如果找到某结点,它是该父节点的右节点
if(node.next!=null)
return node.next;
//如果始终找不到,说明所有节点都没有被访问过,返回null
else
return null;
}
103. 之字形层序遍历二叉树
Given a binary tree, return the zigzag level order traversal of its nodes’ values. (ie, from left to right, then right to left for the next level and alternate between).
思路:
在层序遍历的基础上,每次判断一下该层应该正常输出,还是翻转后输出。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Solution {
public List<List<Integer>> zigzagLevelOrder(TreeNode root) {
List<List<Integer>> res = new ArrayList<>();//存放总的结果
if(root==null) return res;
int flag=1;//记录遍历的方向,1表示从左往右,2表示从右往左
int begin=0;
int end=0;
TreeNode t=null;
ArrayList<TreeNode> que = new ArrayList<>();//模拟存放节点的队列
ArrayList<Integer> one;
que.add(root);//首先将root压入模拟队列
while(begin<que.size()) {
one = new ArrayList<>();//存放某一层的遍历结果
end=que.size();//end表示本层遍历结束
while(begin<end) {
t=que.get(begin);
one.add(t.val);
if(t.left!=null) que.add(t.left);
if(t.right!=null) que.add(t.right);
begin++;
}
if(flag==1) {
flag=2;
res.add(one);
}
else {
flag=1;
Collections.reverse(one);
res.add(one);
}
}
return res;
}
}
拓展:层序遍历二叉树
思路:
利用队列:
每次打印当前结点的时候,如果该结点有子结点,则把子结点放到队列的末尾;
打印完当前节点后,弹出队首节点,重复前面的打印操作,直至队列变空。
void PrintFromTopToBottom(BinaryTreeNode *pTreeRoot)
{
if(!pTreeRoot)
return;
deque<BinaryTreeNode*> dequeTreeNode;
dequeTreeNode.push_back(pTreeRoot);
while(dequeTreeNode.size())//若队列不为空
{
BinaryTreeNode *pNode = dequeTreeNode.front();
dequeTreeNode.pop();
printf("%d " , pNode->m_nValue);
//如果当前节点有子节点
if(pNode->m_pLeft)
dequeTreeNode.push_back(pNode->m_pLeft);
if(pNode->m_pRight)
dequeTreeNode.push_back(pNode->m_pRight);
}
}
拓展:层序遍历并逐层输出
在层序遍历的基础上,还要求每层单独输出,每层的顺序是从左往右。
思路:
我们必须知道每层元素的开始和结束,这种情况下使用vector容器是更好的选择。使用两个变量分别标识每一层的开始和结束位置,控制对每一层元素的访问。
下面的代码中,cur表示当前层的开始节点,last表示最后节点。
vector<Node *> vec;
void TravelByLevel(Node *root)
{
if(root == NULL)
return;
vec.push_back(root);
int cur = 0;
int last = 1;
while(cur < vec.size()){
last = vec.size();
//一层的开始
while(cur < last){
cout << vec[cur] << " ";
if(vec[cur]->left) {
vec.push_back(vec[cur]->left);
}
if(vec[cur]->right) {
vec.push_back(vec[cur]->right);
}
cur++;
}
cout << endl;
}
}
199. 二叉树的最右侧元素
Given a binary tree, imagine yourself standing on the right side of it, return the values of the nodes you can see ordered from top to bottom.
For example:
Given the following binary tree,
1 <---
/ \
2 3 <---
\ \
5 4 <---
You should return [1, 3, 4].
思路:
依然是属于层序遍历。在前一个问题的基础上,当检测到当前节点是所在层的最后一个节点时,就可以把它存入result中了。
class Solution {
public:
vector<int> result;
vector<TreeNode* > level;
vector<int> rightSideView(TreeNode* root) {
if(!root) return result;
level.push_back(root);
int cur=0;
int last=1;
while(cur<level.size()){
last=level.size();
while(cur<last){
if(cur==(last-1))
result.push_back(level[cur]->val);
if(level[cur]->left)
level.push_back(level[cur]->left);
if(level[cur]->right)
level.push_back(level[cur]->right);
cur++;
}
}
return result;
}
};
235. 二叉排序树中寻找最小公共祖先(LCA)
思路:
二叉排序树的性质决定了:如果p,q 比root小,则LCA必定在左子树;如果p,q比root大, 则LCA必定在右子树;如果一大一小, 则root即为LCA。因此可以用递归的方法求解。
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(p->val<root->val && q->val<root->val) return lowestCommonAncestor(root->left,p,q);
else if(p->val>root->val && q->val>root->val) return lowestCommonAncestor(root->right,p,q);
else return root;
}
};
108. 将排序数组转换成平衡二叉树
思路:
递归。
每次找到排序数组的中点,该点左边的子排序数组构成左子树,右边的子排序数组构成右子树。
由于每次都几乎做到了二分,所以最后得到的BST树是平衡的。
边界情况是:
- 若子数组只有一个,就直接返回。
- 若子数组有两个,就返回第一个数,第二个数作为其右子树。
- 若子数组大于两个,就继续递归。
class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
if(nums.size()==0) return NULL;
return r(nums,0,nums.size()-1);
}
TreeNode* r(vector<int>& nums,int p,int q){
if(p>q) return NULL;
int mid = (p+q)/2;
TreeNode *root = new TreeNode(nums[mid]);
root->left = r(nums,p,mid-1);
root->right = r(nums,mid+1,q);
return root;
}
};
230. 求BST中第k个数
思路:
建立两个递归函数,一个用来计算当前节点的所有子节点数;另一个用来求给定树中第k个数。思路如下:
- 计算左子树元素个数n。
- 若k-n=1,则根节点即为第k个元素;
- 若k-n>1, 则第k个元素在右子树中,转为到右子树中寻找第(k-n-1)个元素;
- 若k-n<1,则第k个元素在左子树中,转为到左子树中找第k个元素。
class Solution {
public:
int kthSmallest(TreeNode* root, int k) {
int n=c(root->left);//root的左子树的节点数
if((k-n)==1) return root->val;
if((k-n)>1) return kthSmallest(root->right,k-n-1);
else return kthSmallest(root->left,k);
}
int c(TreeNode* root){//计算根节点root所包含的节点个数
if(root==NULL)
return 0;
else
return 1+c(root->left)+c(root->right);
}
};
输出树的第m层节点
这里只需要输出第m层的节点,不需要使用BFS,而是使用递归。
void f(TreeNode* root,int m)
{
if(root == NULL) return;
if(m==0)
{
cout<<root->val<<endl;
return;
}
f(root->left,m-1);
f(root->right,m-1);
}
116. 链接完全二叉树中每个节点的右节点
思路:
完全二叉树的概念:只有最下面的两层结点度能够小于2,并且最下面一层的结点都集中在该层最左边的若干位置的二叉树。
可以认为是一种前序遍历,即先对当前节点的子节点进行链接,然后再分别对左节点和右节点进一步处理。处理思路为:
- 若左孩子为空,则右孩子一定为空,直接return;
- 若左孩子不为空,则右孩子一定不为空,所以链接左孩子和右孩子即可(左孩子的next赋值为右孩子);
- 由于是前序遍历,所以父节点的next比子节点的next先被设置,故设置右孩子节点的next时,是(利用父节点的next指针)将父节点不同的两个子节点进行连接。
形式上,可以认为是变化过的前序遍历。
class Solution {
public:
void connect(TreeLinkNode *root) {
if(root == NULL || root -> left == NULL) {
return;
}
root->left->next = root->right;
if(root->next != NULL) {
root->right->next = root->next->left;
}
connect(root->left);
connect(root->right);
return;
}
};
117. 链接任意二叉树中每个节点的右节点
思路:
对于任意的二叉树,用层序遍历比较合适。
针对每一行,将前一节点的next指针指向后一元素。
class Solution {
public:
void connect(TreeLinkNode *root) {
if(root==NULL) return;
vector<TreeLinkNode*> que;
int start=0;
int end=1;
que.push_back(root);
TreeLinkNode *t=new TreeLinkNode(0);
TreeLinkNode *pre=NULL;
while(start!=que.size())
{
end=que.size();
pre=NULL;
while(start!=end)
{
t=que[start];
if(pre!=NULL) pre->next=t;
pre=t;
if(t->left!=NULL) que.push_back(t->left);
if(t->right!=NULL) que.push_back(t->right);
start++;
}
}
}
};
98. 判断是否是二叉搜索树(Java)
思路:
首先得到树的中序遍历结果,然后看该结果是否是单调递增的,如果是,就是一个二叉搜索树。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Solution {
public boolean isValidBST(TreeNode root) {
if(root==null) return true;
inorder(root);
for(int i=1;i<list.size();i++) {
if(list.get(i)<=list.get(i-1)) return false;
}
return true;
}
ArrayList<Integer> list=new ArrayList<>();
void inorder(TreeNode root) {
if(root==null) return;
if(root.left!=null) inorder(root.left);
list.add(root.val);
if(root.right!=null) inorder(root.right);
}
}
297. 序列化和反序列化二叉树
题意:
设计一个算法,实现二叉树的序列化与反序列化。如何实现没有限制,只要保证一颗二叉树可以序列化为一个string串,然后这个string串可以反序列化为原来的二叉树即可。
思路:
序列化比较容易,我们做一个层次遍历就好,空的地方用null表示,稍微不同的地方是题目中示例得到的结果是
,即 4 和 5 的两个空节点我们也存了下来。
"[1,2,3,null,null,4,5,null,null,null,null,]"
反序列化时,我们根据逗号“,”分割得到每个节点。需要注意的是反序列化时如何寻找父节点与子节点的对应关系?我们知道在数组中,如果满二叉树(或完全二叉树)的父节点下标是 i,那么其左右孩子的下标分别为 2*i+1 和 2*i+2,但是这里并不一定是满二叉树(或完全二叉树),所以这个对应关系需要稍作修改。如下面这个例子:
5
/ \
4 7
/ /
3 2
/ /
-1 9
序列化结果为
[5,4,7,3,null,2,null,-1,null,9,null,null,null,null,null,]
。
其中,节点 2 的下标是 5,可它的左孩子 9 的下标为 9,并不是 2*i+1=11
,原因在于前面有个 null 节点(4的右孩子节点),这个 null 节点没有左右孩子,所以后面的节点下标都提前了2。所以我们只需要记录每个节点前有多少个 null 节点,就可以找出该节点的孩子在哪里了,其左右孩子分别为 2*(i-num)+1
和 2*(i-num)+2
(num为当前节点之前 null 节点的个数)。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
StringBuilder sb = new StringBuilder();
Queue<TreeNode> queue = new LinkedList<TreeNode>();
queue.offer(root);
while (!queue.isEmpty()) {
TreeNode node = queue.poll();
if (node == null) {
sb.append("null,");
} else {
sb.append(String.valueOf(node.val) + ",");
queue.offer(node.left);
queue.offer(node.right);
}
}
return sb.toString();
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
if (data.isEmpty()) return null;
String[] vals = data.split(",");
int[] nums = new int[vals.length]; // 节点i之前null节点的个数
TreeNode[] nodes = new TreeNode[vals.length];
for (int i = 0; i < vals.length; i++) {
if (i > 0) {
nums[i] = nums[i - 1];
}
if (vals[i].equals("null")) {
nodes[i] = null;
nums[i]++;
} else {
nodes[i] = new TreeNode(Integer.parseInt(vals[i]));
}
}
for (int i = 0; i < vals.length; i++) {
if (nodes[i] == null) {
continue;
}
nodes[i].left = nodes[2 * (i - nums[i]) + 1];
nodes[i].right = nodes[2 * (i - nums[i]) + 2];
}
return nodes[0];
}
}
// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));
124. 最大路径和
给定一颗二叉树,求其最大路径和。路径的起点和终点可以是树中的任意节点。
思路:
如果某节点root正好是某条它所属路径中最高的那个节点(可以是端点,也可以不是;如果是端点,那么就是直上直下;如果不是端点,那么路径就是一个弯曲的弧),那么该路径的和就应该是F(left) + F(right) + val
,当然如果left或者right小于,0就不用加了。
如果该节点是某条路径的端点,那么该路径就应该是root->val
、root->val + lmaxSum
和root->val + rmaxSum
中的最大值。
定义一个辅助变量maxVal,存储递归过程中,以某结点为最高节点的最长路径。所有maxVal中的最大值即为所求。
/**
* Definition for a binary tree node.
* struct TreeNode {
* int val;
* TreeNode *left;
* TreeNode *right;
* TreeNode(int x) : val(x), left(NULL), right(NULL) {}
* };
*/
class Solution {
public:
int maxVal = INT_MIN;
int maxPathSum(TreeNode* root) {
if (root == NULL)
return 0;
maxSum(root);
return maxVal;
}
int maxSum(TreeNode *root)
{
if (root == NULL)
return 0;
/*求以root为最高节点的路径的最大和*/
int curVal = root->val;
int lmaxSum = maxSum(root->left);
int rmaxSum = maxSum(root->right);
if (lmaxSum > 0)
curVal += lmaxSum;
if (rmaxSum > 0)
curVal += rmaxSum;
if (curVal > maxVal)
maxVal = curVal;
/*返回以当前root为端点的路径的最大和*/
return max(root->val, max(root->val + lmaxSum, root->val + rmaxSum));
}
};
二叉树中和为某一值的路径
题目:输入一个整数和一棵二元树。
从树的根结点开始往下访问一直到叶结点所经过的所有结点形成一条路径。
打印出和与输入整数相等的所有路径。
例如 输入整数22和如下二元树
10
/ \
5 12
/ \
4 7
则打印出两条路径:10, 12和10, 5, 7。
思路:
先序遍历树即可得到结果。
算法: PrintPath(TreeNode* pRoot, int sum, const int target) 用来计算,sum为栈中的元素的和,target为目标值。
到达一个节点之后计算当前节点和sum的和,如果为target,输出路径返回,如果大于target,则直接返回,如果小于,则将当前节点的值入栈,更新sum的值,继续遍历,遍历完成之后,也就是从当前节点返回的时候,将其从栈中弹出,更新sum。
struct TreeNode
{
int data;
TreeNode* pLeftChild;
TreeNode* pRightChild;
TreeNode(int a):data(a) {};
};
TreeNode* CreateTree()
{
TreeNode* pRoot = NULL;
char buffer[10];
cin>>buffer;
int a = atoi(buffer);
if(a == 0) return NULL;
else
{
pRoot = new TreeNode(a);
pRoot->pLeftChild = CreateTree();
pRoot->pRightChild = CreateTree();
}
return pRoot;
}
vector<int> stack;
void PrintPath(TreeNode* pRoot, int sum, const int target)
{
if(pRoot == NULL) return;
if(sum + pRoot->data == target)// 如果当前值加上路径和为目标值,则输出
{
for(int i=0; i<stack.size(); i++)
cout<<stack[i]<<"->";
cout<<pRoot->data<<endl;
return;
}
else if(sum + pRoot->data > target)//如果大于目标值,则返回
{
return;
}
else// 如果小于,则入栈
{
stack.push_back(pRoot->data);
PrintPath(pRoot->pLeftChild, sum + pRoot->data, target);
PrintPath(pRoot->pRightChild,sum + pRoot->data,target);
stack.pop_back();
}
}
int main()
{
//从txt文件中读取输入
if(freopen("in.txt", "r", stdin)==NULL) cout<<"in.txt doesn't exist!"<<endl;
freopen("out.txt", "w", stdout);
TreeNode* pTree = CreateTree();
PrintPath(pTree, 0, 22);
return 0;
}
树中两节点的最远距离
思路:
分为两种情况来讨论:
1)二叉树中最远的两个节点经过根节点
2)二叉树中最远的两个节点不经过根节点,在其左子树或者右子树中
所以:二叉树中最远的距离 = 【(左子树距离根节点最远的节点 + 右子树距离根节点最远的节点),左子树中最远的距离,右子树中最远的距离】三者的最大值
int longest_dis(Node* root)
{
if(!root) || ( (root->left == NULL) && (root->right==NULL) )
return 0;
int height1 = height(root->left);
int height2 = height(root->right);
//如果左子树不为空,右子树为空
if(root->left!=NULL && root->right==NULL)
return max(height1+1,longest_dis(root->left));
//如果右子树不为空,左子树为空
if(root->left==NULL && root->right!=NULL)
return max(height2+1,longest_dis(root->right));
return max(height1+height2+2,max(longest_dis(root->left),longest_dis(root->right)));
}
int height(Node *root)
{
if(root == NULL) return 0;
int lHeight = height(root->left);
int rHeight = height(root->right);
return max(lHeight,rHeight)+1;
}
二叉树的镜像
1.递归实现
void MirroRecursively(BinaryTreeNode *pNode)
{
if(NULL == pNode)
return;
if(NULL == pNode->Left && NULL == pNode->Right)
return;
BinaryTreeNode *pTemp = pNode->Left;
pNode->Left = pNode->Right;
pNode->Right = pTemp;
if(pNode->Left)
MirroRecursively(pNode->Left);
if(pNode->Right)
MirroRecursively(pNode->Right);
}
2.非递归实现,即使用循环实现
void MirrorNonRecurively(BinaryTreeNode *pNode)
{
if(NULL == pNode)
return;
stack<BinaryTreeNode *> stackTreeNode;
stackTreeNode.push(pNode);
while(stackTreeNode.size())
{
BinaryTreeNode *pNode = stackTreeNode.top();
stackTreeNode.pop();
if(NULL != pNode->Left || NULL != pNode->Right)
{
BinaryTreeNode *pTemp = pNode->Left;
pNode->Left = pNode->Right;
pNode->Right = pTemp;
}
if(NULL != pNode->Left)
stackTreeNode.push(pNode->Left);
if(NULL != pNode->Right)
stackTreeNode.push(pNode->Right);
}
}
337. 小偷问题 III
The thief has found himself a new place for his thievery again. There is only one entrance to this area, called the “root.” Besides the root, each house has one and only one parent house. After a tour, the smart thief realized that “all houses in this place forms a binary tree”. It will automatically contact the police if two directly-linked houses were broken into on the same night.
Determine the maximum amount of money the thief can rob tonight without alerting the police.
思路:
递归求解,用动态规划的思想,给每个结点增加1个两元素的结构体choice。
choice.no 表示不加上当前结点值的最大值,choice.yes 表示加上当前结点值的最大值。
那么对于 choice.no 来说,其值等于子结点的choice的两个元素的较大值,因为此时可以选择加上子结点,也可以选择不加上子结点,选其中较大的。
但是对于 choice.yes 来说,因为已经加上了当前结点,就不能再加上子结点的值了,因此只能算上子结点的 choice.no了。
struct choice
{
int no;
int yes;
choice():no(0),yes(0){}
};
class Solution {
public:
int rob(TreeNode* root) {
choice ans = getMoney(root);
return max(ans.no, ans.yes);
}
choice getMoney(TreeNode* node) {
choice ans;
if(node == NULL) return ans;
//leftNode存储左子树的两种情况,rightNode存储右子树的两种情况
choice leftNode = getMoney(node->left);
choice rightNode = getMoney(node->right);
//如果不加当前节点,则左右子节点可加可不加,取较大的
ans.no = max(leftNode.no,leftNode.yes) + max(rightNode.no,rightNode.yes);
//如果加入当前节点,则子节点不能加
ans.yes = leftNode.no + rightNode.no + node->val;
return ans;
}
};
遍历多叉树
递归版
void travel(Node *pNode)
{
if (pNode == Null)
return;
Deal(pNode);
for (int i=0 ;i<pNode->child_list.size(); i++)
{
Node *tmp = pNode->child_list[i];
travel(tmp);
}
}
非递归版:
void travel(Node *pNode)
{
stack stack;
stack.push(pNode);
Node *lpNode;
while(!stack.empty())
{
lpNode = stack.top();
stack.pop();
Deal(lpNode);
for (int i=0 ;i<pNode->child_list.size(); i++)
{
stack.push(pNode->child_lis[i]);
}
}
}
打印出多叉树中所有路径
vector<TreeNode*> path;
void printPathsRecur(TreeNode* pRoot) {
if (pRoot == Null)
return;
//如果是叶子节点,打印路径
if (pRoot->child_list.size() == 0)
{
printArray(path);
return;
}
path.push_back(pRoot);
for (int i=0 ;i<pRoot->child_list.size(); i++)
{
TreeNode *tmp = pRoot->child_list[i];
printPathsRecur(tmp);
}
path.pop_back();
}
打印出多叉树中到某一个节点的路径
vector<TreeNode*> path;
bool printPathsRecur(TreeNode* pRoot, TreeNode* pNode)
{
if (pRoot == Null)
return false;
//如果到达了该节点
if (pRoot == pNode)
return true;
path.push_back(pRoot);
bool found = 0;
for (int i=0 ;!found && i<pRoot->child_list.size(); i++)
{
TreeNode *tmp = pRoot->child_list[i];
found = printPathsRecur(tmp);
}
if(!found)
path.pop_back();
return found;
}