【PTA刷题】甲级 树的遍历

根据数组建树

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%。
在这里插入图片描述

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;
	}
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值