【算法刷题】树和二叉树题型及方法归纳

1、树和二叉树的特点

(1)二叉树

二叉树是有左、右孩子的树,存储方式有顺序存储和链式存储。

二叉树的链式存储

struct TreeNode {
	int val;
	TreeNode *left;
	TreeNode *right;
	TreeNode() : val(0), left(nullptr), right(nullptr) {}
	TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
	TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
};

做题思路:

  1. 对于递归条件要明确三部曲:(1)确定传递参数和返回值;(2)确定终止条件;(3)确定内部处理逻辑。
  2. 对于二叉树一类的题,重点是要明确对于空结点、叶子结点、左孩子结点、右孩子结点分别要做哪些处理。
    (1)通常遇到空节点返回NULL0res(结果集)。遇到叶子结点进行某种操作或判定后,再返回。遇到左、右孩子结点,进行某种操作或判定后,再继续向下遍历。
    (2)对于返回类型为TreeNode*的常会用到返回的结点进行判定孩子指针指向返回的节点root->left=traversal(root->left))。
    (3)对于求最值或求有无时,可结合一个全局变量进行求解。需对之前的结点值或当前的结点值进行存储时,常结合一个局部变量作为函数入口参数进行存储。
  3. (1)先序遍历: 适于自顶向下获取信息;
    (2)后序遍历: 适于自底向上获取信息,底层结点会将元素信息返回给父节点;
    (3)中序遍历: 适用于像BST这种,具有单调性的数列,常结合双指针来解题或将其按序存入vector中;
    (4)层次遍历: 适用于对某一层进行操作或

(2)树

树的孩子结点个数不局限于两个。

(3)二叉树的遍历方式

深度优先遍历:前、中、后序(递归或迭代法用栈模拟)
广度优先遍历:层次遍历(用队列模拟)

43、【树和二叉树】二叉树的先、中、后和层次遍历方法合集(C/C++版)

1)先序遍历

递归
先序遍历的递归方式,常采用一个全局变量深拷贝的变量进行配合加减,在末尾会返回这个变量并退出。

void preorderTraversal(TreeNode *root){
	if(root == NULL)	return ;

	visit(root->data);
	preorderTraversal(root->left);
	preorderTraversal(root->right);
}

非递归

void preorderTraversal(TreeNode* root) {
	if(root == NULL)			return ;
	stack<TreeNode*> st;
	st.push(root);
	while(!st.empty()) {         
		TreeNode* node = st.top();
		st.pop();
		visite(node->val);
		if(node->right != NULL)		st.push(node->right);		// 因为栈是先进后出,因此先压右,再压左,可保证弹出为先左后右
		if(node->left != NULL)		st.push(node->left);
	}
}

二叉树的先序遍历——递归+非递归

2)中序遍历

递归
常用于BST相关,递增序列的题。

void inorderTraversal(TreeNode *root){
	if(root == NULL)		return ;

	inorderTraversal(root->left);
	visit(root->data);
	inorderTraversal(root->right);
}

非递归

void inorderTraversal(TreeNode* root){
	stacck<TreeNode*> st;
	TreeNode* p = root;	
	while(p != nullptr || !st.empty()){
		while(p != nullptr){
			st.push(p);
			p = p->left;
		}
		p = st.top();
		st.pop();
		visit(node->data);
		p = p->right;
	}
}

二叉树的中序遍历——递归+非递归

3)后序遍历

后序遍历的递归方式,通常需要对底层的结点处理后,将信息返回给上一层结点,上一层的结点需要用到下面的信息进行返回。结点间的信息处理,通过利用栈返回的数值进行处理

注:先序遍历,结点间的信息处理,通过一个深拷贝变量或全局变量进行处理。

递归

void postorderTraversal(TreeNode* root){
    if(root == NULL)        return ;

    postorderTraversal(root->left);
    postorderTraversal(root->right);
    visit(root->data);
}

非递归

void postorderTraversal(TreeNode* root){
	if(root == nullptr)     return {};
	stack<TreeNode*> st;
	st.push(root);
	// 遍历一个,存一个,先按照中右左的顺序存储
	while(!st.empty()) {
		TreeNode* node = st.top();
		st.pop();
		visit(node->data);
		if(node->left != nullptr)       st.push(node->left);
		if(node->right != nullptr)      st.push(node->right);
	}
	// 反转,将中右左变成左右中
	reverse(res.begin(), res.end());
	return res;
}

二叉树的后序遍历——递归+非递归

4)层次遍历

void levelorder(TreeNode* root){
/**
 * Definition for a binary tree node.
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode() : val(0), left(nullptr), right(nullptr) {}
 *     TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
 *     TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
 * };
 */
class Solution {
public:
    void levelOrder(TreeNode* root) {
        if(root == nullptr)         return {};
        queue<TreeNode*> que;
        que.push(root);
        while(!que.empty()) {
            // 获取一层的结点个数,遍历该层所有结点
            int n = que.size();
            while(n--) {
                TreeNode* node = que.front();
                que.pop();
                visit(node->val);
                // 将下一层的左右孩子存入队列中
                if(node->left != nullptr)      que.push(node->left);
                if(node->right != nullptr)     que.push(node->right);                
            }
        }
        return ;
    }
};
}

二叉树的层次遍历、107.二叉树的层次遍历 II

2、二叉树和N叉树的属性

求值:求平均值、最大最小值、最右侧值、左叶子之和、最左下角叶子

求路径:求所有路径、求满足某一条件的路径、N叉树的遍历

  1. 每层结点的平均值
    637.二叉树的层平均值
    层次遍历,确定每层结点之和与结点数,相除之后,将结果加入结果集。

  2. 每层最大值
    515. 在每个树行中找最大值
    迭代:层次遍历,确定每层结点数,找到这几个结点中的最大值,加入结果集当中。

  3. 每层最右端元素
    199. 二叉树的右视图
    迭代:层次遍历,遍历到最后一个数时加入到结果集当中。

  4. 填充每个结点下一个右侧结点
    116. 填充每个节点的下一个右侧节点指针
    117. 填充每个节点的下一个右侧节点指针 II
    迭代:层次遍历。非尾结点,next指向续结点;尾结点,next指向NULL

  5. 层次遍历N叉树,对vector进行for遍历获取孩子结点。
    429. N 叉树的层序遍历
    迭代:层次遍历。注意存储格式,int valvector childern。对vector

  6. 求二叉树的所有路径
    95、【树与二叉树】leetcode ——257. 二叉树的所有路径:递归法DFS/回溯法+迭代法DFS+层序遍历BFS(C++版本)
    递归:先序遍历(回溯法)
    迭代:先序遍历(两个递归栈)、层次遍历(两个队列)

  7. 求有某一路径之和满足目标值
    98、【树与二叉树】leetcode ——112. 路径总和:5行精简代码回溯法[带剪枝]+迭代法(C++版本)
    递归:先序遍历
    迭代:先序遍历(两个递归栈)

  8. 所有左叶子之和
    96、【树与二叉树】leetcode ——404. 左叶子之和:递归法[先序+后序]+迭代法[先序+层次](C++版本)
    根据左叶子特征判定出左叶子后,将左叶子的值加入到结果集中。
    递归:先序遍历
    迭代:先序遍历、层次遍历

  9. 最左下角的叶子
    97、【树与二叉树】leetcode ——513.找树左下角的值:层次遍历+回溯法(C++版本)
    递归:先序遍历:设置一个全局高度记录,遇到第一个更大高度的,就记录该叶子,一定为最左端叶子。
    迭代:层次遍历

3、二叉树的深度与高度

先序遍历求解的是深度(从根到下),后序遍历求解的是高度(从下到根)。当所求的一个结点的深度=高度时,先序和后续均可用。
递归方式求深度:一个局部变量depth记录当前深度,一个全局变量res整个数的最大或最小深度。
递归方式求高度: 获取左高度left,右高度right,再把结果返回给返回给上一层结点

深度: res = max(res, depth)
高度: return max(left, right) + 1;

求最大深度递归或迭代都可以,求最小深度优先用层序遍历方式。
求高度用后序遍历。

  1. 求整个二叉树的最大深度
    90、【栈与队列】leetcode ——104. 二叉树的最大深度:层次遍历+DFS+子问题分解(C++版本)
    递归:(1)先序遍历:回溯法;(2)后序遍历:子问题分解
    迭代:层次遍历

  2. 求N叉树的最大深度
    91、【栈与队列】leetcode ——559.n叉树的最大深度(递归DFS+迭代BFS)(C++版本)
    递归:(1)先序遍历:回溯法;(2)后序遍历:子问题分解,获取每个结点的最大深度,结果传给上一层。
    迭代:层序遍历

  3. 求整个二叉树的最小深度
    92、【栈与队列】leetcode ——111. 二叉树的最小深度:层次遍历+先序DFS+后序DFS[子问题分解](C++版本)
    递归:(1)先序遍历:回溯法;(2)后序遍历:子问题分解
    迭代:层序遍历(优选)

  4. 求各结点高度差是否满足AVL条件
    94、【树与二叉树】leetcode ——110. 平衡二叉树(C++版本)
    递归:后序遍历,求高度差和高度

4、二叉树的修改与构造

(1)左右双指针方法

分别向左子树和右子树遍历,后序方式获取两边的返回值,传给上一层结点。

  1. 交换二叉树的每层结点
    88、【树与二叉树】leetcode ——226. 翻转二叉树:先中后序的递归与DFS非递归遍历+BFS层次遍历(C++版本)
    类比于数组,每层中两个结点交换
    递归:先序遍历、中序遍历、后序遍历
    迭代:三序、层次遍历

  2. 查看是否为对称二叉树(以根为中轴对称)
    89、【树与二叉树】leetcode ——101. 对称二叉树:后序递归+迭代法+层次遍历(C++版本)
    设置左右双指针进行遍历
    递归:后序遍历
    迭代:后序遍历、层次遍历

  3. 根据完全二叉树性质,求完全二叉树的节点个数
    93、【树与二叉树】leetcode ——222. 完全二叉树的节点个数:普通二叉树求法+完全二叉树性质求法(C++版本)
    设置左右双指针遍历最左侧和最右侧深度,判定此时结点以下为完全二叉树还是满二叉树
    递归:后序遍历(完全二叉树性质求法)、先序遍历(普通二叉树求法)
    迭代:层次遍历(普通二叉树性质求法)、先序遍历(普通二叉树求法)

(2)二叉树的划分构造

根据中序+前/后/层次遍历构造二叉树:根据后三个,可以先得到根节点,然后再根据根节点,再在中序序列中划分出左右子树序列。依据此划分,再在后三个中,划分出对应的左右子序列。然后,再分别将对应的左子树序列和右子树序列向下遍历。

  1. 中序+后序构造二叉树
    前序+中序构造二叉树
    100、【树与二叉树】leetcode ——105. 从前序与中序遍历序列构造二叉树+106. 从中序与后序遍历序列构造二叉树(C++版本)

  2. 每次找到数组中最大值作为根节点,再将左侧作为左子树,右侧作为右子树,递归构造。
    102、【树与二叉树】leetcode ——654. 最大二叉树(C++版本)

  3. 合并两个二叉树
    101、【树与二叉树】leetcode ——617. 合并二叉树:递归法+迭代法(C++版本)

5、BST二叉搜索树

BST是一个规定了数值大小排列顺序的树,即 左子树结点 < 当前结点 < 右子树结点

(1)二叉搜索树的属性

  1. 搜索二叉树中某一数值,若没有则返回NULL。
    103、【树与二叉树】leetcode ——700. 二叉搜索树中的搜索:递归法+迭代法(C++/Python版本)

  2. 判定是否为BST树
    104、【树与二叉树】leetcode ——98. 验证二叉搜索树:递归法[先序+中序+后序]+迭代法(C++版本)

  3. 返回任意两结点之间的差值
    105、【树与二叉树】leetcode ——530. 二叉搜索树的最小绝对差:中序遍历递归法+迭代法(C++版本)

  4. 寻找到BST中出现频率最高的树
    106、【树与二叉树】leetcode ——501. 二叉搜索树中的众数:双指针法+哈希表法(C++版本)

  5. 找到二叉树中任意两结点的最近公共祖先
    107、【树与二叉树】leetcode ——236. 二叉树的最近公共祖先:回溯法(C++版本)

(2)二叉树公共祖先问题

  1. 找到BST中任意两结点的最近公共祖先
    108、【树与二叉树】leetcode ——235. 二叉搜索树的最近公共祖先:普通树解法+BST性质解法(C++版本)

  2. 在BST中插入一个元素
    109、【树与二叉树】leetcode ——701. 二叉搜索树中的插入操作:递归法+双指针迭代法(C++版本)

(3)二叉搜索树的修改与构造

  1. 删除BST中的指定元素结点
    110、【树与二叉树】leetcode ——450. 删除二叉搜索树中的节点:递归法+迭代法(C++版本)

  2. 修剪BST,使BST中仅含有规定范围的元素
    111、【树与二叉树】leetcode ——669. 修剪二叉搜索树:递归法(C++版本)

  3. 将有序数组转化为BST
    112、【树与二叉树】leetcode ——108. 将有序数组转换为二叉搜索树:二分查找树(C++版本)

  4. 累加树:每个结点的新值 ≥ node.val值之和
    113、【树与二叉树】leetcode ——538. 把二叉搜索树转换为累加树:递增数组视角右中左遍历(C++版本)

你可能感兴趣的:(数据结构与算法刷题,#,树与二叉树,算法,数据结构,排序算法)