根据数组建树
TreeNode* CreateTree(int a[],int len,int index)
{
TreeNode* root = new TreeNode;
if (index >= len) //要特别注意不要数组越界
return nullptr;
if (a[index] == -999)
return nullptr;
else
{
root->val = a[index];
root->left = NULL;
root->right = NULL;
}
root->left = CreateTree(a, len, 2 * index + 1);
root->right = CreateTree(a, len, 2 * index + 2);
return root;
}
void preorder(TreeNode* root)//用前序遍历验证
{
if (root == NULL)
return;
cout << root->val<<" ";
preorder(root->left);
preorder(root->right);
}
int main()
{
//use -999 as an impossible number to replace NULL
int a[8] = { 1, -2, -3, 1, 3, -2, -999, -1 };
TreeNode* root = CreateTree(a, 8, 0);
preorder(root);
return 0;
}
前中后三种遍历
递归写法比较简单。主要学习迭代写法。
【前序】 来自leedcode 144.
前序递归:
class Solution {
public:
vector<int> res;
void preorder(TreeNode* root)
{
if(root == NULL)
return;
res.push_back(root->val);
preorder(root->left);
preorder(root->right);
return;
}
vector<int> preorderTraversal(TreeNode* root) {
preorder(root);
return res;
}
};
前序迭代:
深度优先,为了避免浅层的右子树的干扰,采用栈这种后进先出的容器来储存。需要注意的是,对某个根节点,一定是right子树先压栈。这样出栈的时候永远是先处理左边的节点了。
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
vector<int> res;
if (root == NULL)
return res;
TreeNode* p;
stack<TreeNode* > S;//深度优先,要用栈,忽略浅层的右子树影响
S.push(root);
while( !S.empty())
{
p = S.top();
S.pop();
res.push_back(p->val);
if (p->right) S.push(p->right);//栈是先进后出,所以右子树先进
if (p->left) S.push(p->left);
}
return res;
}
};
【中序】来自leedcode 94.
中序递归:
class Solution {
public:
vector<int> res;
void inorder(TreeNode* root)
{
if(root == NULL)
return;
inorder(root->left);
res.push_back(root->val);
inorder(root->right);
}
vector<int> inorderTraversal(TreeNode* root) {
inorder(root);
return res;
}
};
中序迭代:
向左访问冲到底,一路压栈,直到冲到空的。这时候就弹回一格到结点A。访问完A就可以出栈了(事实上出栈这一下就相当于第二次访问了,等同于弹回上一个根节点),这时候A的左子树和A本身都被访问过了(即走过了左->根,还差个右),所以随即检查一下右子树。(如果是空的就没有压栈过程,直接出下一个栈。不空的话就处理右儿子的子树去了)。用PTA的二次访问思想也好理解。第一次经过的时候是压栈,第二次经过的时候是出栈打印。
此外一个小特性:压栈过程是前序遍历,出栈过程是中序遍历(实际上就是先根再左还是先左再根的关系)
先序遍历先打印根结点,因此需要在入栈时就打印;而中序遍历先打印的是左子结点,因此需要先入栈,直到无结点可入的时候弹出的结点就是当前子树的根节点了。
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
stack<TreeNode*> S;
vector<int> v;
TreeNode* rt = root;
while(rt || S.size()){
while(rt){
S.push(rt);
rt=rt->left;
}
rt=S.top();S.pop();
v.push_back(rt->val);
rt=rt->right;
}
return v;
}
};
【后序】
后序迭代:
先序是 根->左->右 后序是左->右->根
所以只需要把模仿根->右->左(其实只是改变了push的顺序),最后再reverse就可以得到后序遍历序列。
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
vector<int> ans;
if(root == NULL) return ans;
stack<TreeNode*> s;
s.push(root);
while(!s.empty()){
TreeNode* p = s.top();
ans.push_back(p->val);
s.pop();
if(p->left) s.push(p->left);//左边先进,形成根右左
if(p->right) s.push(p->right);
}
reverse(ans.begin(), ans.end());//颠倒别漏了
return ans;
}
};
作者:yizhe-shi
链接:https://leetcode-cn.com/problems/binary-tree-preorder-traversal/solution/c-san-chong-jie-fa-by-yizhe-shi-3/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
leetcode100:相同的树 前序遍历变型,深度优先递归
class Solution {
public:
bool isSameTree(TreeNode* p, TreeNode* q) {
if (p == NULL && q == NULL) //同为空
return true;
if (p == NULL || q == NULL) //仅一个为空
return false;
if (p->val != q->val)
return false;
//对当前(根节点做判断,满足终止条件即返回)
return isSameTree(p->right, q->right) && isSameTree(p->left, q->left);//不满足终止条件则去探究其左右子树。
}
};
LeetCode 101.对称二叉树 前序遍历,双指针
和上题类似,不过用到了双指针。用P,Q两个指针一开始都指向根节点(为了保证只有根节点的情况下也能正确判断)。然后q往右移,p往左移,依次递归判断。再p往右移,q往左移,这样就可以判断所有的镜像点了。
class Solution {
public:
bool check(TreeNode* p,TreeNode* q){
if(p == NULL && q == NULL)
return true;
if(p == NULL || q == NULL)
return false;
return (p->val == q->val) && check(p->right,q->left) && check(p->left,q->right);//注意第一个括号里面的最好写在返回值里面
}
bool isSymmetric(TreeNode* root) {
return check(root,root);//一开始指向同一个根节点
}
};
层序遍历
LeetCode 102 二叉树层序遍历, 二维数组的push_back
主要卡在二维数组push上,事实上分两步:1。每轮push进一个空的vector 2.每轮内依次在数组最尾端,即back()处添加push_back。
back()和end()的区别是end()还在back()后面,指向的地方没有任何元素,代表了vector的终结。而back()代表vector的最后一个元素。
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
queue<TreeNode* > Q;
TreeNode* p;
vector<vector<int>> res;
if (root == NULL)
return res;
Q.push(root);//根节点入队
while (!Q.empty())
{
res.push_back(vector<int>()); //推入一个空一维数组
int LevelSize = Q.size(); //这层有多少个元素
for (int i = 0; i < LevelSize; i++)
{
p = Q.front(); //出队
Q.pop();
res.back().push_back(p->val);//把值放到数组的末尾的位置
if (p->left) Q.push(p->left);
if (p->right) Q.push(p->right);
}
}
return res;
}
};
还有一些变形的题目,如求树的最大深度之类的,只需要在for里面稍微修改一部分即可。
前中/中后序遍历构建二叉树
常见用法:
递归写法核心是找到左子树大小。每次递归的参数就是当前这轮前序的左指针PreLeft和右指针PreRight,以及中序的InLeft,InRight。然后在递归内通过前序的开头第一个就是根的特性,到中序中去找根的位置In_root,这样的话,就可以表示出这轮前序和中序中的左子树,右子树和根的所有区间编号了。
这里通过构建哈希表来提高了查找中序中根位置的效率。
此外,传入数组的时候用传地址的方式来做,可以极大的提高时间和空间效率。
//前中序建树
class Solution {
private:
map<int, int> index;//快速返回中序遍历中值和位置脚标的关系
public:
TreeNode* myBuildTree(vector<int>& preorder, vector<int>& inorder, int preleft, int preright, int inleft, int inright)
{
if (preleft > preright) // 倒数第一层的时候已经被切分到只有一格了,因此preleft = preright
return nullptr; // 递归size_left = 0,所以preleft +1,preright没有加,最后一层就会是preleft>preright,返回一个空指针
int pre_root = preleft;//前序根节点就是第一个
int in_root = index[ preorder[pre_root] ];//根据根的值来找位置
TreeNode* root = new TreeNode(preorder[pre_root]);//建立树的根
int size_left = in_root - inleft;//确定左子树个数
//递归遍历左右子树并分别连接
root->left = myBuildTree(preorder, inorder,
preleft + 1, preleft + size_left, inleft, in_root - 1);
root->right = myBuildTree(preorder, inorder,
preleft + size_left + 1, preright, in_root + 1, inright);
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int n = preorder.size();
for (int i = 0; i < n; i++)
index[inorder[i]] = i; //构建位置哈希表
return myBuildTree(preorder, inorder, 0, n - 1, 0, n - 1);
}
};
中后序建树:
class Solution {
private:
map<int, int> index;
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postoreder,
int behindleft,int behindright,int inleft, int inright)
{
if (behindleft > behindright)
return nullptr;
int behind_root = behindright;
int in_root = index[postoreder[behind_root]];
TreeNode* root = new TreeNode(postoreder[behind_root]);//build the root
int left_size = in_root - inleft;
root->left = buildTree(inorder,postoreder,behindleft,behindleft + left_size - 1,inleft,in_root - 1);
root->right = buildTree(inorder, postoreder, behindleft + left_size, behindright - 1, in_root + 1, inright);
return root;
}
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {
int n = postorder.size();
for (int i = 0; i < n; i++)
index[inorder[i]] = i;
return buildTree(inorder, postorder, 0, n - 1, 0, -n - 1);
}
};
PTA 1119.前后序遍历,求得中序遍历
题目大意:给你前序后序遍历,让你求中序遍历。并且判断这种中序遍历是不是唯一的。(例如前序1 2,后序2 1,就有两种情况—>2节点在1的左边,2节点在1节点的右边)。为了处理这种情况,我们在建树的时候,就整体把当前处理的片段一股脑归入右子树。这样就取得了一种中序遍历的情况。
注意:
1.核心还是preleft,preright,postleft,postright这四个点。与常规题型不同的是,这里需要用preleft+1来定位Post里面的脚标。此外,还要用postright-1来找到pre里面的脚标。这样的话,就有两种方式来表达left_size。如果left_size是相等的,则说明一直没有左右的歧义。如果不相等,则把当前处理的片段完整地丢入右子树中。
//pta 1119
#include <iostream>
#include <vector>
#include <map>
using namespace std;
struct TreeNode
{
int val;
TreeNode* left, *right;
};
TreeNode* NewNode(int v)
{
TreeNode* root = new TreeNode;
root->val = v;
root->left = root->right = NULL;
return root;
}
int pre[35], post[35];
map<int, int> post_index;
map<int, int> pre_index;
int IsUnique = 1;
TreeNode* BuildTree(int preleft,int preright,int postleft,int postright)
{
if (preleft > preright)
return nullptr;
TreeNode* root = NewNode(pre[preleft]);//建根
int test = root->val;
if (preleft == preright) //只有一个节点了,立刻返回
return root;
if (pre[preleft] == post[postright]) //pre的第一和post的最后相同
{
int i = post_index[pre[preleft + 1]]; //根据左1找左右分界
int left_size = i - postleft+1;
int j = pre_index[post[postright - 1]];//根据右-1找左右分界
if (j - preleft - 1 == left_size) //如果分别在pre和post中两种长度的表示方法相等
{
root->left = BuildTree(preleft + 1, preleft + left_size, postleft, i);
root->right = BuildTree(j, preright, i + 1, preright - 1);
}
else
{ //如果不相等,把当前一段一股脑全部放进右子树。
root->right = BuildTree(preleft + 1, preright, postleft, postright - 1);
IsUnique = 0;
}
}
else
{
root->right = BuildTree(preleft + 1, preright, postleft, postright - 1);
IsUnique = 0;
}
return root;
}
void Post_initial(int a[],int N) //post存脚标
{
for (int i = 0; i < N; i++)
post_index[a[i]] = i;
}
void Pre_initial(int a[], int N)//pre存脚标
{
for (int i = 0; i < N; i++)
pre_index[a[i]] = i;
}
vector<int> res;
void inorder(TreeNode* root)
{
if (root == NULL) return;
inorder(root->left);
res.push_back(root->val);
inorder(root->right);
}
int main()
{
int N;
cin >> N;
for (int i = 0; i < N; i++)
cin >> pre[i];
for (int i = 0; i < N; i++)
cin >> post[i];
Pre_initial(pre, N);
Post_initial(post, N); //初始化map
TreeNode* root = BuildTree(0, N - 1, 0, N - 1);
inorder(root);
if (IsUnique == 1)
cout << "Yes\n";
else
cout << "No\n";
for (int i = 0; i < N; i++)
{
if (i == 0) cout << res[i];
else cout << " " << res[i];
}
cout << "\n";
return 0;
}
PTA 1086.push就是前序,pop就是中序
25 min
问题的关键是要清楚中序遍历的过程中,push的顺序就是前序遍历(第一次经过结点即打印),pop出来的顺序就是中序遍历。所以题目实际上是考察了前序和中序遍历构建树,然后再后序遍历整出数组。
#include <stdio.h>
#include <stack>
#include <iostream>
#include <vector>
#include <queue>
#include <map>
#include <string>
using namespace std;
struct TreeNode
{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) : val(x), left(NULL), right(NULL) {}
};
vector<int> result;
map<int, int> Index;
stack<int> S;//用来做输入的堆栈
vector<int> inorder;
vector<int> preoreder;
TreeNode* buildTree(int preleft, int preright, int inleft, int inright)//前序和中序建树
{
if (preleft > preright)
return nullptr;
int pre_root = preleft;
int in_root = Index[ preoreder[pre_root] ];//中序中找到根的位置
TreeNode* root = new TreeNode( preoreder[pre_root] );
int left_size = in_root - inleft;
root->left = buildTree(preleft + 1, preleft + left_size, inleft,in_root - 1);
root->right = buildTree(preleft + left_size + 1, preright, in_root + 1, inright);
return root;
}
void postorder(TreeNode* root)//后序遍历
{
if (root == NULL)
return;
postorder(root->left);
postorder(root->right);
result.push_back(root->val);
}
int main()
{
int N;
cin >> N;
string operation;
int tmp,num;
for (int i = 0; i < 2 * N; i++)
{
cin >> operation;
if (operation == "Push")
{
cin >> tmp;
S.push(tmp);
preoreder.push_back(tmp);
}
else if (operation == "Pop")
{
num = S.top();
S.pop();
inorder.push_back(num);
}
}
for (int i = 0; i < N; i++) //先遍历一遍中序,把值和位置的关系找出来
Index[inorder[i]] = i;
TreeNode* root = buildTree(0, N - 1, 0, N - 1);
postorder(root);
for (int i = 0; i < N; i++)
{
if (i == 0)
cout << result[i];
else
cout << " " << result[i];
}
return 0;
}
综合题:PTA 1102 (树的静态数组表示)
实际上静态数组表示二叉树非常方便,开一个足够大空间的装有结构体的数组,往里面填入结点的左右儿子即可。
题目中需要反转一棵树,只要在构建树的时候左右互换储存即可。如3的左儿子本来是1,右儿子本来是2。互换之后就变成了3的右儿子是1,左儿子是2。此外,如果常规建树,利用后序遍历的 左->右->根 特性,在[根]这一步用swap交换,也可以达到反转的目的。(实际上是一个原理)。
本题还有一个要注意的地方:在静态数组中找根。----->先建一个哈希表,一开始默认所有结点都是根节点。在存储的时候同时进行判断,如果某结点出现在左右儿子的数组中(即作为儿子结点出现,就不可能是根了),不断排除,最后只可能剩下一个节点的角标。那个角标就是根节点。
#include <stdio.h>
#include <stack>
#include <iostream>
#include <vector>
#include <queue>
#include <map>
#include <string>
using namespace std;
const int maxnum = 110;
struct TreeNode
{
int left;
int right;
};
TreeNode a[maxnum];//静态数组表示二叉树
bool notRoot[maxnum] = { false };//一开始都是根节点
int Trans(char target)
{
if (target == '-')
return -1;
else
{
notRoot[target - '0'] = true;//实锤了是某结点的儿子,就不会是根了
return target - '0';
}
}
vector<int> res_Levelorder;
void Levelorder(int root)
{
int p;
queue<int> Q;
Q.push(root);
while (!Q.empty())
{
p = Q.front();
Q.pop();
res_Levelorder.push_back(p);
if (a[p].left != -1) Q.push(a[p].left);
if (a[p].right != -1) Q.push(a[p].right);
}
return;
}
vector<int> res_Inorder;
void Inorder(int root)
{
if (root == -1)
return;
Inorder(a[root].left);
res_Inorder.push_back(root);
Inorder(a[root].right);
}
int main()
{
char Left, Right;
int N;
cin >> N;
for (int i = 0; i < N; i++)
{
cin >> Left >> Right;
a[i].left = Trans(Right);//反转,颠倒左右结点的储存
a[i].right = Trans(Left);
}
int root;
for (int i = 0; i < N; i++)
if (notRoot[i] == false)
root = i; //找到根
Levelorder(root);//层序遍历
Inorder(root);//中序遍历
for (int i = 0; i < N; i++)
{
if (i == 0)
cout << res_Levelorder[i];
else
cout << " " << res_Levelorder[i];
}
cout << "\n";
for (int i = 0; i < N; i++)
{
if (i == 0)
cout << res_Inorder[i];
else
cout << " " << res_Inorder[i];
}
return 0;
}
PTA 1079:供应链(DFS遍历树)
耗时:33min
这题和1090异曲同工。都是开一个大数组存树 ,然后用DFS或者BFS遍历,在叶节点(即跳出条件)那里根据题目的具体要求来做文章。注意DFS的话深度是可控的,可以将深度作为参数传入DFS计算中。因为每次进一层递归,其实就是深了一层。
1.另外,找寻根节点,只需要判断哪个点没有出现在儿子输入处,因为不作任何人的儿子,就是根。
2.pow(x,y) --------> x的y次幂
#include <stdio.h>
#include <stack>
#include <iostream>
#include <vector>
#include <queue>
#include <map>
#include <string>
#include <math.h>
using namespace std;
const int maxnum = 100005;
struct Node
{
int amount;
vector<int> child;
};
Node a[maxnum];//静态树
bool notRoot[maxnum] = { false };//默认全是根
double P, r;
double result = 0.0;
int FindRoot(int N)
{
for (int i = 0; i < N; i++)
{
if (notRoot[i] == false)
return i;
}
}
void DFS(int index,int depth)
{
if (a[index].child.size() == 0) //没有儿子,则是叶节点
{
result += a[index].amount * pow(r, depth) * P;//pow(x,y)->x的y次幂
return;
}
for (int i = 0; i < a[index].child.size(); i++)
{
DFS(a[index].child[i], depth + 1);
}
}
int main()
{
int N;
cin >> N >> P >> r;
r = r / 100 + 1.00;
for (int i = 0; i < N; i++) //构建树
{
int childNum;
cin >> childNum;
if (childNum == 0)
cin >> a[i].amount;
else
{
for (int j = 0; j < childNum; j++)
{
int tmp;
cin >> tmp;
a[i].child.push_back(tmp);
notRoot[tmp] = true;//做过儿子,则不可能是根节点了
}
}
}
int root = FindRoot(N);
DFS(root, 0);
printf("%.1lf", result);
return 0;
}
PTA 1094:层序遍历算同辈(BFS遍历树)
耗时:30min
基本上就是把二叉树的层序遍历改了一下,只要注意些BFS内部的时候,由于是二维数组,脑袋别犯浑就行。注意分别好“节点”这个概念(在本题中节点就是一个个vector,vector里面存有儿子结点的角标),因此,push儿子(如果有的话)的时候是 a[p[j]],p[j]是儿子的角标,a[p[j]]是儿子结点。(反正类比二叉树就完事了,仔细点没啥难度)。
#include <stdio.h>
#include <stack>
#include <iostream>
#include <vector>
#include <queue>
#include <map>
#include <string>
#include <math.h>
using namespace std;
const int maxnum = 105;
vector<int> a[maxnum];
vector<int> level_num;//每层有多少人
void BFS(int root)//1是根
{
if (a[root].size() == 0)//空树情况
return;
queue<vector<int> > Q;
Q.push(a[root]);
vector<int> p;
while ( ! Q.empty())
{
int LevelSize = Q.size();
level_num.push_back(LevelSize);
for (int i = 0; i < LevelSize; i++)//按层出队
{
p = Q.front(); Q.pop();
if (p.size() != 0)//该节点有儿子
{
for (int j = 0; j < p.size(); j++)
Q.push(a[p[j]]);
}
else
continue; //叶子节点,没有儿子可供进队列
}
}
}
int main()
{
int N, M;
cin >> N >> M;
if (M == 0)//处理特殊情况(只有一辈)
{
cout << N << " " << 1;
return 0;
}
for (int i = 0; i < M; i++)
{
int tmp,childN;
cin >> tmp >> childN;
for (int j = 0; j < childN; j++)
{
int childID;
cin >> childID;
a[tmp].push_back(childID);
}
}
int root = 1;
BFS(root);
int MaxPopu = 0, MaxGen = -1;
for (int i = 0; i < level_num.size(); i++)
{
if (level_num[i] > MaxPopu)
{
MaxPopu = level_num[i];
MaxGen = i;
}
}
cout << MaxPopu <<" "<< MaxGen + 1;//题目中root那一层被定义为1了
return 0;
}
PTA 1004:数树叶 (BFS遍历树)
耗时:16min
比较简单
#include <stdio.h>
#include <stack>
#include <iostream>
#include <vector>
#include <queue>
#include <map>
#include <string>
#include <math.h>
using namespace std;
const int maxnum = 105;
vector<int> a[maxnum];
int noChild[105] = {0};
int BFS(int root)
{
queue<vector<int> >Q;
Q.push(a[root]);
vector<int> p;
int PresentLevel = -1;
while (!Q.empty())
{
PresentLevel++;
int level_size = Q.size();
for (int i = 0; i < level_size; i++)
{
p = Q.front(); Q.pop();
if (p.size() == 0)
noChild[PresentLevel]++;//某结点没儿子,该层叶节点+1
else
{
for (int j = 0; j < p.size(); j++)
Q.push(a[p[j]]);
}
}
}
return PresentLevel;
}
int main()
{
int N, M;
cin >> N >> M;
for (int i = 0; i < M; i++)
{
int tmp, childN;
cin >> tmp >> childN;
for (int j = 0; j < childN; j++)
{
int childID;
cin >> childID;
a[tmp].push_back(childID);
}
}
int root = 1;
int MaxLevel = BFS(root);
for (int i = 0; i <= MaxLevel; i++)
{
if (i == 0)
cout << noChild[i];
else
cout << " " << noChild[i];
}
return 0;
}
PTA 1053:权重与路径 (DFS)
耗时:93min
本体非常有代表性, 主要难点在DFS如何表示出当前的路径和权重。题解中是没遍历到一个点,就进行一次sum的判断,如果相等,就进一步判断是否是叶子节点。否则返回。同时用深度depth来表示出path数组的第depth个元素。这样即便深度遍历到不符合的点返回上一级时,path也会得到返回,输入新的路径。
总结:
1.需要递归时每次返回上一级时,还原某个参数(如本题中的depth和sum),只需要把这个参数作为输入变量并做改变即可。(这样倒回上一层的时候,变量就是原来没进入这层函数的值,这样就相当于“还原”了)。
2.题解在储存的时候就进行了权重判断排序,十分巧妙,大大减少了程序复杂度。
#include <stdio.h>
#include <stack>
#include <iostream>
#include <vector>
#include <queue>
#include <map>
#include <string>
#include <math.h>
#include <algorithm>
using namespace std;
const int maxnum = 105;
int N, M, S;
struct Node
{
int weight;
vector<int> child;
};
Node a[maxnum];
vector<int> path(maxnum);//判断符合条件一次输出一次,输出后清空
int tmp;//变动sum时,用于储存老sum值,便于返回
void DFS(int root,int depth,int sum)
{
if (sum > S)
return;
else if (sum == S)
{
if (a[root].child.size() == 0)//确定是叶子节点
{
for (int i = 0; i < depth; i++)//此时path里面是一条可行的路径
{
if (i == depth - 1) cout << path[i]<<"\n";
else cout << path[i]<<" ";
}
return;
}
else
return;//不是叶子结点,直接返回
}
else
{
if (a[root].child.size() != 0)
{
for (int i = 0; i < a[root].child.size(); i++)//遍历该节点的全部子节点
{
path[depth] = a[a[root].child[i]].weight;
DFS(a[root].child[i], depth + 1, sum + a[a[root].child[i]].weight);
}
}
else
return;
}
}
bool cmp(int id1,int id2)
{
return a[id1].weight > a[id2].weight;
}
int main()
{
cin >> N >> M >> S;
for (int i = 0; i < N; i++)
cin >> a[i].weight;
for (int i = 0; i < M; i++)
{
int tmp,childN;
cin >> tmp >> childN;
for (int j = 0; j < childN; j++)
{
int childID;
cin >> childID;
a[tmp].child.push_back(childID);
}
sort(a[tmp].child.begin(), a[tmp].child.end(), cmp);//儿子中权重大的放在前面储存
}
int root = 0;
path[0] = a[0].weight;
DFS(0, 1, a[0].weight);
return 0;
}
PTA 1115 Counting Nodes in a BST
用时:18Min
送分题
#include <iostream>
#include <vector>
#include <queue>
using namespace std;
struct TreeNode
{
int val;
TreeNode* left, *right;
};
TreeNode* NewNode(int v)
{
TreeNode* root = new TreeNode;
root->val = v;
root->left = NULL;
root->right = NULL;
return root;
}
void Insert(TreeNode* &root,int v)
{
if (root == NULL)
{
root = NewNode(v);
return;
}
if (v <= root->val)
Insert(root->left, v);
else
Insert(root->right, v);
}
vector<vector<int>> res;
void levelorder(TreeNode* root)
{
if (root == NULL)
return;
queue<TreeNode*> Q;
Q.push(root);
TreeNode* p;
while (!Q.empty())
{
res.push_back(vector<int>());
int level_size = Q.size();
for (int i = 0; i < level_size; i++)
{
p = Q.front(); Q.pop();
res.back().push_back(p->val);
if (p->left) Q.push(p->left);
if (p->right) Q.push(p->right);
}
}
}
int main()
{
int N;
cin >> N;
TreeNode* root = NULL;
for (int i = 0; i < N; i++)
{
int v;
cin >> v;
Insert(root,v);
}
levelorder(root);
int res_size = res.size();
int nearlast, last;
if (res_size >= 2) {
nearlast = res[res_size - 2].size();
last = res[res_size - 1].size();
}
else if (1 == res_size)
{
last = 1;
nearlast = 0;
}
else if (0 == res_size)
{
last = 0;
nearlast = 0;
}
cout << last << " + " << nearlast << " = " << last + nearlast;
return 0;
}
力扣113.路径总和(异曲同工,注意类内数组定义即可)
class Solution {
public:
vector<int > tmp = vector<int> (9999); //这样在类中就不会引起二义性了,vector<int> tmp(99)会被认为是一个返回array的方法
/*vector<int> a{ vector<int>(999) }; 也可以*/
vector<vector<int> > result;
void DFS(TreeNode* root, int depth, int sum,int pathsum)
{
tmp[depth] = root->val;
if (root->left == NULL && root->right == NULL)
{
if (pathsum == sum) // This node is right
{
vector<int> path;
for (int i = 0; i < depth + 1; i++)
path.push_back(tmp[i]);
result.push_back(path );//push 'tmp' into 2D 'result' array
return;
}
else
{
return;
}
}
else//not leaf, just push the val in 'tmp' array
{
if (root->left)
DFS(root->left, depth + 1, sum, pathsum + root->left->val);
if (root->right)
DFS(root->right, depth + 1, sum, pathsum + root->right->val);
}
}
vector<vector<int>> pathSum(TreeNode* root, int sum) {
if (root == NULL) return result;
DFS(root, 0, sum, root->val);
return result;
}
};
@[TOC](1130 Infix Expression (25point(s)))
用时:fail
大意,给你一个树,让你按照要求输出。
要点:主要是没法处理括号的问题,一开始一直在想是否是depth影响括号。 后来看题解,只需要简单的判断每个节点的儿子情况,分情况写if来讨论返回的string值。在涉及返回儿子的string值时,用dfs递归就可以了。 还是暴露了对递归返回值不熟悉的缺点。这一点需要开专题集中训练。
#include <iostream>
#include <vector>
using namespace std;
int n;
const int maxnum = 25;
int IsRoot[maxnum]; //找根用的数组
struct Node
{
string data;
int left, right;
};
Node vec[maxnum]; //静态树
string DFS(int root)
{
if (vec[root].left == -1 && vec[root].right == -1) //左右儿子都空
return vec[root].data;
else if (vec[root].left == -1 && vec[root].right != -1) //左空右不空
return "(" + vec[root].data + DFS(vec[root].right) + ")";
else if (vec[root].left != -1 && vec[root].right != -1)//左右都不空
return "(" + DFS(vec[root].left) + vec[root].data + DFS(vec[root].right) + ")";
}
int main()
{
cin >> n;
fill(IsRoot, IsRoot + maxnum, true); //初始化每个节点都是根
for (int i = 1; i <= n; i++)
{
Node node;
cin >> node.data >> node.left >> node.right;
vec[i] = node;
IsRoot[node.left] = false;
IsRoot[node.right] = false; //排除掉两个不是根
}
int root;
for (int i = 1; i <= n; i++)
if (IsRoot[i] == true)
root = i;
string str = DFS(root);
if (str[0] == '(')
str = str.substr(1, str.length() - 2);//最后去掉最外层的括号
cout << str;
return 0;
}
力扣116.填充每个节点的下一个右侧节点指针(直接指)
第一时间肯定想到层序遍历。但实际上能不用层序遍历这种要额外容器的,就不要用。要习惯直接指向的递归思想。
class Solution {
public:
void myConnect(Node* left,Node* right) {
if (left)
{
left->next = right;
myConnect(left->left, left->right);
myConnect(left->right, right->left);
myConnect(right->left, right->right);
}
}
Node* connect(Node* root) {
if(root == NULL) return root;
myConnect(root->left,root->right);
return root;
}
};
层序遍历写法(自己写的,效率贼低)
class Solution {
public:
Node* connect(Node* root) {
Node* p;
queue<Node* > Q;
if (root == NULL)
return nullptr;
Q.push(root);
vector<Node* > vec;
while (!Q.empty())
{
int level_size = Q.size();
vec.clear();
for (int i = 0; i < level_size; i++)
{
p = Q.front(); Q.pop();
vec.push_back(p);
if (p->left) Q.push(p->left);
if (p->right) Q.push(p->right);
}
for (int i = 1; i < level_size; i++)
vec[i - 1]->next = vec[i];
vec[level_size - 1]->next = nullptr;
}
return root;
}
};
力扣236.二叉树找公共祖先
如果是搜索二叉树可以利用大小性质比较简单:
自顶向下遍历,
1.如果p的值和q的值都比当前节点值小,则说明p,q都在左子树上,下一步往左子树遍历即可。
2.如果都比当前值大,则说明p,q都在右子树上,下一步往右子树遍历即可。
3.如果一个小于一个大于,则当前节点就是最近公共祖先。
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if(root == NULL) return NULL;
int rootval = root->val;
int pv = p->val;
int qv = q->val;
if (pv > rootval && qv > rootval)
return lowestCommonAncestor(root->right, p, q);
else if (pv < rootval && qv < rootval)
return lowestCommonAncestor(root->left, p, q);
else
return root;
}
};
如果是普通的二叉树的情况则比较复杂,需要利用左右子树是否包含目标节点来做。然后通过判断当前节点的左右子树是否符合左右都包含p或q节点,如果都包括则当前节点就是最近公共祖先
1.找到跳出返回条件:当前节点为空或当前节点为p,q中的一个
2.向上返回,用一个left和right分别接住。
3.返回后,进行判断。当前节点的左右子节点都不空,则当前节点就是要找的公共节点。若有至少一个空,则返回另一个
class Solution {
public:
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
if (root == NULL || root == p || root == q)//跳出条件:当前节点是空,或等于p,q
return root;
TreeNode* left = lowestCommonAncestor(root->left, p, q);//找左子树
TreeNode* right = lowestCommonAncestor(root->right, p, q);//找右子树
if (left && right) //若左右都不空(各包含p和q),则当前节点就是公共主先
return root;
if (left != NULL)//如果左边包含,右边不包含,返回左子树
return left;
else
return right;//反之同理
//两边都是空也包含在这种情况里面了,返回一个right,此时right是空
//其实和标准题解一个思路,只不过用NULL来代表了BOOL的false更简洁
}
};
树的动态规划
力扣337.打家劫舍
一开始想着是层序遍历,隔层取,结果自然是错了。
理解以下代码:
我们使用爷爷、两个孩子、4 个孙子来说明问题
首先来定义这个问题的状态
爷爷节点获取到最大的偷取的钱数呢
首先要明确相邻的节点不能偷,也就是爷爷选择偷,儿子就不能偷了,但是孙子可以偷
二叉树只有左右两个孩子,一个爷爷最多 2 个儿子,4 个孙子
根据以上条件,我们可以得出单个节点的钱:
4 个孙子偷的钱 + 爷爷的钱 VS 两个儿子偷的钱 哪个组合钱多,就当做当前节点能偷的最大钱数。这就是动态规划里面的最优子结构。由于是二叉树,这里可以选择计算所有子节点(让所有节点都做一次爷爷)
递归到最后相当于是从底层算到高层,最后算到root就是最后结果了。每次返回的result相当于是子树的最优解,这样就包含了所有可能的情况。(包括一个右儿子和两个左孙子的情况)。每次算出的最优解都保存在了hash表中,用节点的地址值和该节点的子树最优取值相绑定。这样多次计算的时候就不用再进行重复的递归计算了。
class Solution {
public:
map<TreeNode*, int> m; //用来存放节点可以偷的钱,避免重复计算。
int rob(TreeNode* root) {
if (root == NULL) return 0;
if (m.find(root) != m.end())//之前计算过这个节点,直接读取数值。
return m[root];
int yesun = root->val;
if (root->left)
{
yesun += rob(root->left->left) + rob(root->left->right);
}
if (root->right)
{
yesun += rob(root->right->left) + rob(root->right->right);
}
int result = max(yesun, rob(root->left) + rob(root->right));
m[root] = result;
return result;
}
};
层序遍历和递归DFS效率对比
LeetCode515.在每个树行中找最大值
层序递归比较容易,但DFS效率要高出50%。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/5a5bfbb100ec5d72f1cfa734a105881e.png)
class Solution {
public:
vector<int> result;
void levelorder(TreeNode* root,int level) //当前在第level层里玩
{
if (root == NULL) return;
if (level >= result.size()) //第一次访问该层
result.push_back(root->val);
else
{
int max_val = max(result[level], root->val);
result[level] = max_val;
}
levelorder(root->left, level + 1);
levelorder(root->right, level + 1);
}
vector<int> largestValues(TreeNode* root) {
if (root == NULL)
return{};
levelorder(root,0);
return result;
}
};
二叉搜索树的中序遍历
LeetCode530. 二叉搜索树的最小绝对差
大意:给你一棵所有节点为非负值的二叉搜索树,请你计算树中任意两节点的差的绝对值的最小值。
好多题都是利用二叉搜索树中序遍历递增这个性质。这题不能用vec来存,不然效率太低了。注意题干中说明了全是非负的节点值,所以可以用last >= 0来控制第一次不做运算。
class Solution {
public:
int last = -1,Min = INT_MAX;
void inorder(TreeNode* root)
{
if (root == NULL)
return;
inorder(root->left);
if(last >= 0)
Min = min(Min, abs(root->val - last));
last = root->val;
inorder(root->right);
}
int getMinimumDifference(TreeNode* root) {
if (root == NULL) return 0;
inorder(root);
return Min;
}
};