文章目录
- 二叉树基础
- leetcode 49 字母异位词分组
- leetcode 98 验证二叉搜索树
- 二叉搜索树的第k大节点
- 二叉搜索树的第k小节点
- 分行从上到下打印二叉树 简单
- 不分行从上到下打印二叉树
- 二叉树的镜像
- 剑指offer 34 && leetcode 113 二叉树中和为某一值的路径 中等
- leetcode 129 求根节点到叶子节点的数字之和
- leetcode 102 二叉树的层序遍历
- leetcode 103 二叉树的锯齿形层序遍历 经典字节题
- leetcode 662 二叉树的最大宽度【面】
- leetcode 513 找二叉树左下角的值
- leetcode 96 不同的二叉搜索树 `DP`
- leetcode 95 不同的二叉搜索树II
- leetcode 106 从中序和后序遍历序列构造二叉树
- 剑指offer 07 重建二叉树 即 从前序遍历和中序遍历构建二叉树
- leetcode 114 二叉树展开为单链表 `中等`
- 二叉树的最大、最小深度 easy
- leetcode 230 二叉搜索树中第k小的元素 `M`
- leetcode 206 二叉树的最近公共祖先
- 剑指offer 26 树的子结构
- leetcode 110 & 剑指offer 55-II 判断一棵树是不是平衡二叉树
二叉树基础
【王道考研笔记一】
1、除根节点之外,任何一个节点都有且仅有一个前驱节点。树是一种递归定义的数据结构;
2、节点、树的属性:
节点层次(深度):从上往下数
节点的高度:从下往上数
树的高度(深度):总共多少层
节点的度:有几个孩子(分支)
树的度:各节点的度的最大值
3、有序树–逻辑上看,树中节点的各子树从左至右是有次序的,不能互换
无序树–逻辑上看,树中节点的各子树从左至右是无次序的,可以互换
4、森林–森林是m(m>=0)棵互不相交的树的集合;
5、树的常考性质
1)节点数=总度数+1
2)m叉树—每个节点最多只能有m个孩子的树
3)
4)
6、二叉树的基本概念
1)二叉树是n(n>=0)的节点的有限集合:
n=0时,是空二叉树
由一个根节点和两个互不相交的被称为根的左子树和右子树组成。左子树和右子树又分别是一颗二叉树
。
2)特点
递归定义的数据结构
每个节点至多只有两颗子树
左右子树不能颠倒(二叉树是**度为2的有序树
**)
7、二叉树的种类:满二叉树和完全二叉树
1)满二叉树:如果一棵二叉树只有度为0的结点和度为2的结点,并且度为0的结点在同一层上,则这棵二叉树为满二叉树。
百度百科
:除最后一层无任何子节点外,每一层上的所有节点都有两个子节点的二叉树;或,一个二叉树,每一层的节点数都达到最大值;
特点:
只有最后一层有叶子节点
不存在度为1的节点
按层序从1开始编号,节点i的左孩子为2i,右孩子为2i+1
,节点i的父节点为i/2;
如下图
2)完全二叉树:在完全二叉树中,除了**最底层节点**可能没填满外,其余每层节点数都达到最大值,并且最下面一层的节点都集中在该层最左边的若干位置
。若最底层为第 h 层,则该层包含 1~ 2h 个节点。
二叉搜索树(二叉排序树 二叉查找树 BST)
概念:
若它的左子树不空,则**左子树上所有结点的值均小于它的根结点
的值;
若它的右子树不空,则右子树上所有结点的值均大于它的根结点
**的值;
它的左、右子树也分别为二叉排序树
性质:二叉搜索树的中序遍历数组是一个递增有序数组 (二叉搜索树是一个有序树)
平衡二叉搜索树:AVL树
(平衡二叉树)
(C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树)红黑树
具有以下性质:
它是一棵空树或它的**左右两个子树的高度差(或深度差)的绝对值不超过1
**,并且左右两个子树都是一棵平衡二叉树。
二叉树的存储方式:链式存储或顺序存储
链式存储方式就用指针
, 顺序存储的方式就是用数组
。
二叉树的顺序存储结构只适合存储完全二叉树,很少用顺序存储的方式存储一颗二叉树;
顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在散落在各个地址的节点串联一起。
链式存储如图:
顺序存储如图:
用数组存储二叉树(顺序存储的方式)如何遍历?
「如果父节点的数组下表是i,那么它的左孩子就是i * 2 + 1,右孩子就是 i * 2 + 2
。」
二叉树的遍历算法方式【重点】
遍历:按照某种次序把所有的节点都访问一遍
主要有两种遍历方式:
深度优先遍历DFS 广度优先遍历BFS
。这两种遍历是图论中最基本的两种遍历方式
深度优先遍历:先往深走 遇到叶子节点再往回走
前序遍历(递归法,迭代法)
中序遍历(递归法,迭代法)
后序遍历(递归法,迭代法)
【记忆方法:中间节点的顺序就是所谓的遍历方式】
前序遍历:中左右
中序遍历:左中右
后序遍历:左右中
广度优先遍历:一层一层的去遍历
层次遍历(迭代法):基于树的层次特性确定的次序规则遍历
区分二叉树节点的高度和深度:
二叉树节点的深度:指从根节点到该节点的最长简单路径边的条数。
二叉树节点的高度:指从该节点到叶子节点的最长简单路径边的条数。
leetcode中强调的深度和高度很明显是按照节点来计算的。
关于根节点的深度究竟是1 还是 0,不同的地方有不一样的标准,leetcode的题目中都是以节点为一度,即根节点深度是1。但维基百科上定义用边为一度,即根节点的深度是0,我们暂时以leetcode为准(毕竟要在这上面刷题)。
因为求深度可以从上到下去查 所以需要前序遍历(中左右),而高度只能从下到上去查,所以只能后序遍历(左右中)
二叉树节点的代码定义(链式存储的二叉树节点的定义方式)
C++代码:
//二叉树节点定义方式
//链式存储的节点方式
struct TreeNode
{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}
};
方法论 递归思想--栈机制
递归的实现:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是为什么递归可以返回上一层位置的原因
。
递归的底层实现是栈
【递归算法三要素】
1、确定递归函数的参数和返回值:确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数,并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。
2、确定终止条件:写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。
3、确定单层递归的逻辑:确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。
前序遍历:
//前序遍历
class Solution
{
public:
void traversal(TreeNode* cur, vector<int>& vec)
{
if (cur == NULL)return;
vec.push_back(cur->val);//访问根节点
traversal(cur->left, vec);//先序遍历左子树
traversal(cur->right, vec);//先序遍历右子树
}
vector<int>preorderTraversal(TreeNode* root)
{
vector<int>res;
traversal(root, res);
return res;
}
};
中序遍历:
//中序遍历
class Solution
{
public:
void traversal(TreeNode* cur, vector<int>& vec)
{
if (cur == NULL)return;
//vec.push_back(cur->val);
traversal(cur->left, vec);
vec.push_back(cur->val);
traversal(cur->right, vec);
}
vector<int>preorderTraversal(TreeNode* root)
{
vector<int>res;
traversal(root, res);
return res;
}
};
后序遍历:
/后序遍历
class Solution
{
public:
void traversal(TreeNode* cur, vector<int>& vec)
{
if (cur == NULL)return;
//vec.push_back(cur->val);
traversal(cur->left, vec);
traversal(cur->right, vec);
vec.push_back(cur->val);
}
vector<int>preorderTraversal(TreeNode* root)
{
vector<int>res;
traversal(root, res);
return res;
}
};
分步分析递归的过程:
1、确定递归函数的参数和返回值
因为要打印出前序遍历节点的数值,所以参数里需要传入vector在放节点的数值,除了这一点就不需要在处理什么数据了也不需要有返回值,所以递归函数返回类型就是void
void traversal(TreeNode* cur, vector<int>& vec)
2、确定终止条件
在递归的过程中,如何算是递归结束了呢,当然是当前遍历的节点是空了,那么本层递归就要要结束了,所以如果当前遍历的这个节点是空,就直接return
if (cur == NULL)return;
3、确定单层递归的逻辑
前序遍历是中左右的循序,所以在单层递归的逻辑,是要先取中节点的数值
vec.push_back(cur->val);
traversal(cur->left, vec);
traversal(cur->right, vec);
以上三种遍历方式都是用递归实现的,现在尝试用迭代法实现:
迭代法(Iteration)是一种不断用变量的旧值递推出新值的解决问题的方法
迭代法求前序遍历:
前序遍历是中左右,每次处理的节点是中间节点,那么先将根节点放入栈中,然后将右孩子加入栈,再加入左孩子;
为什么每次先加入的是右孩子,再加入的是左孩子呢?因为这样出栈的时候才是中左右的顺序
,栈的特点是先进后出;
//迭代法求前序遍历
class Solution
{
public:
vector<int>preorderTraversal(TreeNode* root)
{
stack<TreeNode*>s;
vector<int>res;
s.push(root);
while (!s.empty())
{
TreeNode* node = s.top();//中 创建一个节点记录栈顶元素
s.pop();
if (node != NULL)res.push_back(node->val);
else continue;
s.push(node->right);//右 先放入右子树 再放入左子树
s.push(node->left);//左
}
return res;
}
};
在迭代的过程中,其实我们有两个操作:
处理:将元素放进result数组中
访问:遍历节点
因为前序遍历的顺序是中左右,先访问的元素是中间节点,要处理的元素也是中间节点,所以刚刚才能写出相对简洁的代码,因为要访问的元素和要处理的元素顺序是一致的,都是中间节点
中序遍历(迭代法)
中序遍历是左中右,先访问的是二叉树顶部的节点,然后一层一层向下访问,直到到达树左面的最底部,再开始处理节点
(也就是在把节点的数值放进result数组中),这就造成了处理顺序和访问顺序是不一致的)
解决方法:使用迭代法写中序遍历,就需要借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素。
//用迭代法求中序遍历
class Solution
{
public:
vector<int>inorderTraversal(TreeNode* root)
{
vector<int>res;
stack<TreeNode*>s;
TreeNode* cur = root;//创建一个指针指向根节点
while (cur != NULL || !s.empty())
{
if (cur != NULL)//指针来访问根节点 访问到最底层
{
s.push(cur);//将指针访问到的节点入栈
cur = cur->left;//左
}
else
{
cur = s.top();//从栈里弹出的数据就是要处理的数据
s.pop();
res.push_back(cur->val);
cur = cur->right;//右
}
}
return res;
}
};
后序遍历迭代法
再来看后序遍历,先序遍历是中左右,后续遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后再反转result数组,输出的结果顺序就是左右中了
后序遍历只需要前序遍历的代码稍作修改就可以了
//迭代法求后序遍历
class Solution
{
public:
vector<int>preorderTraversal(TreeNode* root)
{
stack<TreeNode*>s;
vector<int>res;
s.push(root);
while (!s.empty())
{
TreeNode* node = s.top();//中
s.pop();
if (node != NULL)res.push_back(node->val);
else continue;
s.push(node->left);//右
s.push(node->right);//左
}
reverse(res.begin(), res.end());
return res;
}
};
补充一些知识;
【优先级队列
】是一个披着队列外衣的堆。因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。
缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。
定义:priority_queue<Type, Container, Functional>
Type 就是数据类型
,Container 就是容器类型
(Container必须是用数组实现的容器,比如vector,deque等等,但不能用 list。STL里面默认用的是vector),Functional 就是比较的方式
。
当需要用自定义的数据类型时才需要传入这三个参数,使用基本数据类型时,只需要传入数据类型,默认是大顶堆
。
1 //升序队列,小顶堆
2 priority_queue <int,vector<int>,greater<int> > q; //队首元素是最小的元素
3 //降序队列,大顶堆
4 priority_queue <int,vector<int>,less<int> >q;
//或者默认缺省方式 大顶堆方式 队首元素是最大的元素
priority_queue<int,vector<int>>q; //
【堆】堆是一颗完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。
如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆
。
从小到大排就是小顶堆,从大到小排就是大顶堆
【有关容器的基础知识、底层实现】
栈stack
和队列queue
是STL(C++标准库)里面的两个数据结构。
栈stack提供push 和 pop
等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。不像是set 或者map 提供迭代器iterator
来遍历所有元素
栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制使用哪种容器来实现栈的功能。
STL中栈往往不被归类为容器,而被归类为container adapter(容器适配器)。
栈的内部结构,栈的底层实现可以是vector,deque,list 都是可以的,主要就是动态数组和链表的底层实现
。
动态数组实现栈,动态扩容
的过程:先指定数组的长度,如果数组空间已满,则新建一个比原数组大一倍或者n倍的新数组,然后再复制元素;
C++中map、multimap 、set、multiset的底层实现机制是二叉平衡搜索树,再具体一点是红黑树
。
Unordered_map、unordered_set的底层实现是哈希表
。
leetcode 49 字母异位词分组
提出需求:
用哈希
实现:
#include<iostream>
using namespace std;
#include<vector>
#include<unordered_map>
#include<string>
#include<algorithm>
class Solution
{
public:
vector<vector<string>>groupAnagrams(vector<string>& strs)
{
unordered_map<string, vector<string>>mp;//创建一个哈希表 键是字符串自身 值是一个存储字符串的容器
for (string& str : strs)//范围for循环
{
string key = str;
sort(key.begin(), key.end());//将字符串按字典序排列
mp[key].emplace_back(str);//存储字符串到容器中
}
vector<vector<string>>ans;//保存最后结果的二维数组
for(auto it=mp.begin();it!=mp.end();it++)//迭代器的方式来遍历哈希表
{
ans.emplace_back(it->second);//保存含有相同字母的字符串
}
return ans;
}
};
int main()
{
system("pause");
return 0;
}
leetcode 98 验证二叉搜索树
提出需求:
应用二叉搜索树的中序遍历是一个**递增有序序列**这个核心思想
;
代码实现:
#include<iostream>
using namespace std;
#include<vector>
#include<string>
struct TreeNode
{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(nullptr),right(nullptr){}
};
class Solution
{
private:
vector<int>vec;
void traversal(TreeNode* root)//vec按中序遍历的顺序存储节点
{
if (root == NULL)return;
traversal(root->left);//按中序遍历的方式对二叉树的节点进行调整
vec.push_back(root->val);
traversal(root->right);
}
public:
bool isValidBST(TreeNode* root)
{
vec.clear();
traversal(root);
for (int i = 1; i < vec.size(); i++)
{
if (vec[i] <= vec[i - 1])return false;//不符合二叉搜索树递增有序的特点
}
return true;
}
};
int main()
{
system("pause");
return 0;
}
二叉搜索树的第k大节点
class Solution
{
public:
vector<int>res;
void dfs(TreeNode* root)//通过中序遍历调整成递增的顺序
{
if (!root)return;
dfs(root->left);
res.push_back(root->val);
dfs(root->right);
}
int kthLargest(TreeNode* root, int k)
{
dfs(root);
reverse(res.begin(), res.end());
return res[k - 1];
}
};
二叉搜索树的第k小节点
class Solution
{
public:
vector<int>res;
void dfs(TreeNode* root)
{
if (!root)return;
dfs(root->left);
res.push_back(root->val);
dfs(root->right);
}
int kthLargest(TreeNode* root, int k)
{
dfs(root);
return res[k - 1];
}
};
分行从上到下打印二叉树 简单
//分行从上到下打印二叉树
//最常见的层序遍历解法
class Solution {
public:
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>>res;
queue<TreeNode*>q;
if(root)q.push(root);
while(!q.empty())
{
vector<int>vec;
int n=q.size();
for(int i=0;i<n;i++)
{
TreeNode*node=q.front();
q.pop();
if(node)vec.push_back(node->val);
if(node->left)q.push(node->left);
if(node->right)q.push(node->right);
}
res.push_back(vec);
}
return res;
}
};
//===============================================================================
//深度优先搜索实现
class Solution
{
public:
vector<vector<int>>levelOrder(TreeNode* root)
{
vector<vector<int>>res;//创建一个二维数组
dfs(root, res, 0);
return res;
}
void dfs(TreeNode* root, vector<vector<int>>& res, int level) //level表示层数
{
if (root == NULL)return;
if (level >= res.size())res.emplace_back(vector<int>());//当层数大于当前元素数量时 /容器容量不够 放入一个空容器 进行扩容
res[level].emplace_back(root->val);//在每一层的容器中加入元素
dfs(root->left, res, level + 1);//递归左子树
dfs(root->right, res, level + 1);//递归右子树
}
};
不分行从上到下打印二叉树
层序遍历
解法
//不分行从上到下打印二叉树
//层序遍历的方式
class Solution
{
public:
vector<int>levelOrder(TreeNode* root)
{
queue<TreeNode*>q;
vector<int>res;
if (root != NULL)q.push(root);
while (!q.empty())
{
TreeNode* node = q.front();
q.pop();
res.push_back(node->val);
if (node->left)
{
q.push(node->left);
}
if (node->right)
{
q.push(node->right);
}
}
return res;
}
};
二叉树的镜像
输入一棵二叉树 输出该二叉树的镜像
//递归实现
//整体思想:把所有的左子树转换为右子树 把所有的右子树转换为左子树
class Solution
{
public:
TreeNode* mirrorTree(TreeNode* root)
{
if (root == nullptr)return nullptr;
TreeNode* temp = root->left;//暂存左子树节点
root->left = mirrorTree(root->right);//用所有的左子树接收所有的右子树
root->right = mirrorTree(temp);//再把所有的左子树赋值给所有的右子树
return root;
}
};
//非递归实现----层次遍历
class Solution
{
public:
TreeNode* mirrorTree(TreeNode* root)
{
queue<TreeNode*>q;
q.push(root);
while (!q.empty())
{
TreeNode* node = q.front();
q.pop();
if (node)
{
q.push(node->left);//将节点放入队列中是为了继续对其他的节点进行操作
q.push(node->right);
TreeNode* temp = node->left;//交换左右子树
node->left = node->right;
node->right = temp;
}
}
return root;
}
};
剑指offer 34 && leetcode 113 二叉树中和为某一值的路径 中等
//二叉树中和为某一值的路径
class Solution
{
private:
vector<vector<int>>res;//二维数组存储若干条路径的和
vector<int>path;//一维数组存储单条路径
public:
vector<vector<int>>pathSum(TreeNode* root, int target)
{
if (root == NULL)return {};
recursion(root, target);
return res;
}
void recursion(TreeNode* root, int target)
{
if (root == NULL)return;//遇到叶子节点 递归终止 开始回溯
path.push_back(root->val);
target -= root->val;//从目标结果开始递减
if (target == 0 && !root->left && !root->right)//找到了符合条件的路径并到达叶子节点
{
res.push_back(path);//将一条路径存放进去
}
//递归左右子树
recursion(root->left, target);
recursion(root->right, target);
path.pop_back();//回溯
}
};
leetcode 129 求根节点到叶子节点的数字之和
字节 中等
计算从根节点到叶子节点生成的所有数字之和
;
//求根节点到叶子节点的数字之和 字节
class Solution
{
public:
int dfs(TreeNode* root, int preSum)//presum作为根节点的值传入进行递归
{
if (root == nullptr)return 0;
int sum = preSum * 10 + root->val;//sum保存前面遍历到的数字之和,最后的结果就是在sum的基础上加上最后的那个数字;
if (root->left == nullptr && root->right == nullptr)return sum;//到达叶子节点 递归终止条件
else
return dfs(root->left, sum) + dfs(root->right, sum);//递归的求左子树和右子树的和
}
int sumNumber(TreeNode* root)
{
return dfs(root, 0);//返回深度搜索的结果就是我们要找的结果
}
};
leetcode 102 二叉树的层序遍历
//二叉树层序遍历
class Solution
{
public:
vector<vector<int>>levelOrder(TreeNode* root)
{
queue<TreeNode*>q;
if (root != NULL)q.push(root);//根节点入队
vector<vector<int>>res;//后续将一维数组vec放入二维数组res中
while (!q.empty())
{
int n = q.size();
vector<int>vec;
for (int i = 0; i < n; i++) //每一层的处理逻辑
{
TreeNode* node = q.front();//头节点
q.pop();
vec.push_back(node->val);//将当前遍历到的节点压入容器
if (node->left)q.push(node->left);//左右节点入队
if (node->right)q.push(node->right);
}
res.push_back(vec);//将一维数组存放到二维数组中
}
//reverse(res.begin(),res.end());//此时返回的是自底向上的层序遍历序列
return res;//返回一个二维数组
//外层数组是层数组 内层数组是每一层数组的具体元素
}
};
leetcode 103 二叉树的锯齿形层序遍历 经典字节题
//经典字节题
//二叉树的锯齿形层序遍历 在层序遍历的基础上将奇数层倒序处理
//锯齿形层序遍历
class Solution
{
public:
vector<vector<int>>ZlevelOrder(TreeNode* root)
{
vector<vector<int>>res;
queue<TreeNode*>q;
if (root != NULL)q.push(root);
int flag = 0;//代表层数 0表示只有根节点那一层
while (!q.empty())
{
int n = q.size();
vector<int>vec;
for (int i = 0; i < n; i++)//单层的处理逻辑
{
TreeNode* node = q.front();
q.pop();
vec.push_back(node->val);
if (node->left)q.push(node->left);
if (node->right)q.push(node->right);
}
if (flag % 2 == 1)//奇数层的处理方式
{
reverse(vec.begin(), vec.end());
}
res.push_back(vec);
flag++;
}
return res;
}
};
leetcode 662 二叉树的最大宽度【面】
给定一棵二叉树,编写一个函数来获取这个树的最大宽度。树的宽度是所有层中的最大宽度;这个二叉树与满二叉树(full binary tree)结构相同,但一些节点为空。
输入:
1
/ \
3 2
/
5
输出: 2
解释: 最大值出现在树的第 2 层,宽度为 2 (3,2)。
层序遍历解法
:
C++实现:
//因为树中是可能存在空节点的 所以要考虑空节点的情况
//二叉树的最大宽度
class Solution
{
public:
int widthOfBinaryTree(TreeNode* root)
{
if (!root)return 0;
queue<pair<TreeNode*, unsigned long long>>q;//无符号长长整型 [-2*63--2*63]范围
int ans = 1;//宽度至少为1
q.push({ root,1 });//root节点的下标为1
while (!q.empty())
{
ans = max(int(q.back().second - q.front().second + 1), ans);
int n = q.size();
for (int i = 0; i < n; i++)
{
TreeNode* node = q.front().first;
unsigned long long pos = q.front().second;//记录node节点的下标
q.pop();
if (node->left)q.push({ node->left,pos * 2 });
if (node->right)q.push({ node->right,pos * 2 + 1 });
}
}
return ans;
}
};
//=============================================================================
class Solution
{
public:
int widthOfBinaryTree(TreeNode* root)
{
if (!root)return 0;
queue<TreeNode*>q;//初始化一个队列
root->val = 1;//先初始化为1 防止出现负数; ->val表示每一个节点的下标索引;
q.push(root);//首先根节点入队
int ans = 0;//初始化一个变量表示每一层的宽度
while (!q.empty())
{
ans = max(q.back()->val - q.front()->val + 1, ans);//更新计算宽度
int temp = q.front()->val - 1;//初始化一个变量用来表示每一层的第一个节点的下标索引
for (int i = q.size(); i > 0; i--)//倒序遍历每一层
{
auto p = q.front();//初始化一个指针指向队首元素
q.pop();
p->val -= temp;//为了防止溢出 每一层的节点统一减去当前层的第一个节点的下标索引, 再加一
if (p->left)
{
p->left->val = 2 * p->val;//左子树的下标索引计算方式
q.push(p->left);
}
if (p->right)
{
p->right->val = 2 * p->val + 1;//右子树的下标索引的计算方式
q.push(p->right);
}
}
}
return ans;
}
};
leetcode 513 找二叉树左下角的值
与leetcode 119 求二叉树的右视图很像
给定一棵二叉树的根节点root,请找出该二叉树的最底层最左边
节点的值;
求二叉树左下角的值是用一个变量来接受这个值;这个值在遍历下一层的时候会被不断的覆盖,从而更新成最后一层最左边的值
;
而求二叉树的右视图,是用一个容器来接收每一层最右侧的节点,需要返回的是一个节点的集合
;
//找二叉树做左下角的值
//层序遍历 解法
class Solution
{
public:
int findBottomLeftValue(TreeNode* root)
{
queue<TreeNode*>q;//初始化一个队列
if (root != NULL)q.push(root);
int res = 0;
while (!q.empty())
{
int n = q.size();
for (int i = 0; i < n; i++)
{
TreeNode* node = q.front();
q.pop();
if (i == 0)res = node->val;//res始终保存每一层最左边的节点值,直至到达最后一层;
//若把这段代码改成if(i==(size-1))res.push_back(node->val) 则改成求二叉树的右视图
if (node->left)q.push(node->left);
if (node->right)q.push(node->right);
}
}
return res;
}
};
leetcode 96 不同的二叉搜索树 DP
给定一个整数n,求恰由n个节点组成且节点值从1到n互不相同
的二叉搜索树的种数?
小注
:
dp[3] 就是元素1为头节点搜索树的数量+元素2为头节点搜索树的数量+元素3为头节点搜索树的数量;
以元素1为头节点搜索树的数量
=右子树有两个元素的搜索树的数量*左子树有0个元素的搜索树的数量。
以元素2为头节点搜索树的数量
=右子树有1个元素的搜索树的数量 * 左子树有1个元素的搜索树的数量;
以元素3为头节点的搜索树的数量
=右子树有0个元素的搜索树的数量*左子树有2个元素的搜索树的数量;
有2个元素的搜索树数量就是dp[2]。
有1个元素的搜索树数量就是dp[1]。
有0个元素的搜索树数量就是dp[0]。
所以dp[3] = dp[2] * dp[0] + dp[1] * dp[1] + dp[0] * dp[2]
递推可以得到: dp[i] += dp[以j为头结点左子树节点数量] * dp[以j为头结点右子树节点数量]
//不同的二叉搜索树 动态规划解法 代码随想录解法
class Solution
{
public:
int numTrees(int n)
{
vector<int>dp(n + 1);//初始化一个dp数组
dp[0] = 1;//最简单情况
for (int i = 1; i <= n; i++)
{
for (int j = 1; j <= i; j++)
{
dp[i] += dp[j - 1] * dp[i - j];//dp[以j为头节点左子树节点数量]*dp[以j为头节点右子数数量]
}
}
return dp[n]; //dp[n]表示以i到n为节点组成的不同的二叉搜索数的数量
}
};
leetcode 95 不同的二叉搜索树II
给定一个整数n,请你生成并返回所有由n个节点组成且节点值从1到n互不相同
的不同的二叉搜索树;
//不同的二叉搜索树II 返回的是具体的不同的二叉搜索树
class Solution
{
public:
vector<TreeNode*>generateTrees(int n)
{
if (n)return generate(1, n);
else return vector<TreeNode*>{};
}
vector<TreeNode*>generate(int left, int right)//左右节点
{
vector<TreeNode*>ans;
if (left > right)
{
ans.push_back(nullptr);
return ans;
}
for (int i = left; i < right; i++) //以当前位置的i 左边小于i的节点构造出左子树节点集合;以右边大于i的节点构造出右子树节点集合;
{
vector<TreeNode*>left_nodes = generate(left, i - 1);//递归构造出左子树节点
vector<TreeNode*>right_nodes = generate(i + 1, right);//递归构造出右子树节点
for (TreeNode* left_node : left_nodes)//取左子树节点
{
for (TreeNode* right_node : right_nodes)//取右子树节点
{
TreeNode* p = new TreeNode(i);//p指针指向第一个开始构造的节点
p->left = left_node;
p->right = right_node;
ans.push_back(p);//p指针起到遍历构造树的作用
}
}
return ans;
}
};
leetcode 106 从中序和后序遍历序列构造二叉树
如下如所示:
根据中序遍历序列和后序遍历序列构造二叉树 方框表示当前节点的元素 下划线表示分割出的区间
struct TreeNode
{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(nullptr),right(nullptr){}
};
//从中序和后序遍历构造二叉树
class Solution
{
public:
TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder)
{
if (inorder.size() == 0 || postorder.size() == 0)return NULL;
return traversal(inorder, postorder);
}
TreeNode*traversal(vector<int>& inorder, vector<int>& postorder)
{
int rootValue = postorder[postorder.size() - 1];//后序遍历的最后一个节点就是根节点
TreeNode* root = new TreeNode(rootValue);
int i;
for (i = 0; i < inorder.size(); i++)
{
if (rootValue = inorder[i])break;//找到根节点在前序遍历中的下标索引 break跳出循环 保留当前的i值进入下面的代码;
}
//构造中序遍历左右子树
vector<int>leftinorder(inorder.begin(), inorder.begin() + i);
vector<int>rightinorder(inorder.begin() + i + 1, inorder.end());
//构造后序遍历左右子树
postorder.resize(postorder.size() - 1);
vector<int>leftPostorder(postorder.begin(), postorder.begin() + leftinorder.size());
vector<int>rightPostorder(postorder.begin() + leftinorder.size(), postorder.end());
//构造左右子树
root->left = traversal(leftinorder, leftPostorder);
root->right = traversal(rightinorder, rightPostorder);
return root; //返回新树的根节点
}
};
剑指offer 07 重建二叉树 即 从前序遍历和中序遍历构建二叉树
题目:
输入某二叉树的前序遍历和中序遍历的结果,请构建该二叉树并返回其根节点;
假设输入的前序遍历和中序遍历的结果中不含重复的数字;
分治算法思路:
递归参数:根节点在前序遍历中的索引root
,子树在中序遍历中的左边界left
,子树在中序遍历的右边界right
;
终止条件:当left>right
,表示已经越过叶子节点,此时返回null;
递推实现:
1、建立根节点node
:节点值为preorder[root]
2、划分左右子树:查找根节点在中序遍历inorder中的索引i
;
3、构建左右子树 开始左右子树递归
C++ 实现:
struct TreeNode
{
int val;
TreeNode* left, * right;
TreeNode(int x):val(x),left(nullptr),right(nullptr){}
};
///分治策略
class Solution
{
vector<int>preorder;
unordered_map<int, int>dic;//哈希 dic存储的是中序遍历中值与索引的映射;
public:
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder)
{
this->preorder = preorder;//this指针区别preorder 指向当前对象buildTree 即可以直接访问成员了
for (int i = 0; i < inorder.size(); i++) //哈希表记录
{
dic[inorder[i]] = i;//key是中序遍历中的值,i是值对应的索引下标
}
return recur(0, 0, inorder.size() - 1); //递归调用
}
private:
TreeNode* recur(int root, int left, int right)
{
if (left > right)return nullptr;//递归终止
TreeNode* node = new TreeNode(preorder[root]);//建立根节点 preorder[0]就是根节点;
int i = dic[preorder[root]];//划分根节点 左子树 右子树 preorder[root]表示根节点的值;i是根节点在中序遍历inorder中的索引;
node->left = recur(root + 1,left, i - 1);//递归构造左子树
node->right = recur(root + i - left + 1, i + 1, right);//递归构造右子树
return node;//回溯并返回根节点
}
};
leetcode 114 二叉树展开为单链表 中等
提出需求:
给你二叉树的根结点 root
,请你将它展开为一个单链表:
展开后的单链表应该同样使用 TreeNode ,其中 `right 子指针指向链表中下一个结点,而左子指针始终为 null` 。
展开后的单链表应该与二叉树 先序遍历 顺序相同。
小注:
一个关键点是,左子树最下最右的节点,是右子树的父节点
;图中 4节点是 5节点的父节点
代码实现:
struct TreeNode
{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(nullptr),right(nullptr){}
};
//右向的后序遍历解法
class Solution
{
public:
TreeNode* temp = nullptr;
void flatten(TreeNode* root)
{
if (root == nullptr)return;//递归终止条件 这里递归的效果是返回到上一层根节点
flatten(root->right);//递归右子树
flatten(root->left);//递归左子树
root->right = temp;//temp用于保存当前节点的右节点(root此时是2,2->right=4=temp
root->left = nullptr;//因为是单链表 所以要将当前根节点左节点置为空
temp = root;//更新temp=4为右子树的根节点,因为4节点是左子树最下最右的节点;
}
};
二叉树的最大、最小深度 easy
从递归的思路理解这个题:
//Leetcode 104 二叉树的最大深度
//问二叉树的深度就是求最大深度
//二叉树的最大深度
class Solution
{
public:
int maxDepth(TreeNode* root)
{
if (!root)return 0;
int l = maxDepth(root->left);
int r = maxDepth(root->right);
return (l > r ? l : r) + 1;
}
};
class Soliution
{
public:
int getDepth(TreeNode* node)
{
if (node == NULL)return 0;
int leftDepth = getDepth(node->left);
int rightDepth = getDepth(node->right);
int depth = 1 + max(leftDepth, rightDepth);
return depth;
}
int maxDepth(TreeNode* root)
{
return getDepth(root);
}
};
//求二叉树的(最大)深度也可以这样解
class Solution {
public:
int maxDepth(TreeNode* root)
{
if(root==NULL)return 0;
else
{
int l=maxDepth(root->left);
int r=maxDepth(root->right);
return l>r?l+1:r+1;
}
}
};
//Leetcode 111 二叉树的最小深度
class Solution
{
public:
int minDepth(TreeNode* root)
{
if (!root)return 0;
int left = minDepth(root->left);
int right = minDepth(root->right);
return (left && right) ? 1 + min(left, right) : 1 + left + right;
}
}
leetcode 230 二叉搜索树中第k小的元素 M
题目理解:求第k小的元素就是求二叉搜索树的中序遍历序列中的第k个元素
二叉搜索树的中序遍历序列是一个递增有序序列;
解法一:
class Solution
{
public:
vector<int>res;
void dfs(TreeNode* root)
{
if (!root)return;
dfs(root->left);//中序遍历
res.push_back(root->val);
dfs(root->right);
}
int kthSmallest(TreeNode* root, int k)
{
dfs(root);
return res[k - 1];
}
};
解法二:
struct TreeNode
{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x) :val(x), left(nullptr), right(nullptr){}
};
//在二叉搜索树中求第k小的元素 就是在二叉搜索树的中序遍历序列中求正数第几个元素
//因为二叉搜索树的中序遍历序列是一个递增有序序列
class Solution
{
public:
int kthSmallest(TreeNode* root, int k)
{
stack<TreeNode*>s;
while (root != nullptr || s.size() > 0)
{
//中序遍历的顺序
while (root != nullptr)
{
s.push(root);
root = root->left;
}
root = s.top();//将遍历到的节点元素入栈
s.pop();//每次栈顶去掉一个元素
--k;//k值就要减一次
if (k == 0)break;//k减为0的时候跳出当前循环 返回此时的根节点值即可
root = root->right;
}
return root->val;
}
};
leetcode 206 二叉树的最近公共祖先
struct TreeNode
{
int val;
TreeNode* left;
TreeNode* right;
TreeNode(int x):val(x),left(NULL),right(NULL){}
};
class Solution
{
public
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q)// p q是进行比较的两个树节点
{
if (root == nullptr)return nullptr;
if (root == p || root == q)return root;//每当遇到p或q时就返回 返回值是p或q
TreeNode* left = lowestCommonAncestor(root->left, p, q);//递归左子树
TreeNode* right = lowestCommonAncestor(root->right, p, q);//递归右子树
if (left != nullptr && right != nullptr)return root;//p q分别位于左子树和右子树的情况
if (left == nullptr && right == nullptr)return nullptr;
return left == nullptr ? right : left;
}
};
剑指offer 26 树的子结构
题目要求:
输入两棵二叉树,判断B是不是A的子结构(约定空树不是任意一个树的子结构)
B是A的子结构,即A中出现和B相同的结构和节点值
;
C++实现:
//树的子结构
struct TreeNode
{
int val;
TreeNode* left, * right;
TreeNode(int x):val(x),left(nullptr),right(nullptr){}
};
class Solution
{
public:
bool isSubstructure(TreeNode* A, TreeNode* B)
{
if (!A || !B)return false;
bool res = false;//初始化为假
if (A->val == B->val)res = doesAHaveB(A, B);//节点相同的话直接进入比较 比较的结果就是B是不是A的子结构
if (!res)res = isSubstructure(A->left, B);//当上面的比较表示B不是A的子结构的情况下;比价 B是不是A的左右子树的子结构;
if (!res)res = isSubstructure(A->right, B);
return res;
}
//判断B是不是A的子结构
bool doesAHaveB(TreeNode* r1, TreeNode* r2)
{
if (!r2)return true;//A存在一个节点 B在对应的位置上没有,肯定是A的子结构; 例如示例中给的 A中节点4的右子树为2 但是B中节点4的右子树为空
if (!r1)return false;//B存在一个节点,A在对应位置上没有,肯定不是子结构
if (r1->val != r2->val)return false;//A,B对应位置节点值不同 肯定不是子结构
return doesAHaveB(r1->left, r2->left) && doesAHaveB(r1->right, r2->right);//比较左子树和右子树;
}
};
leetcode 110 & 剑指offer 55-II 判断一棵树是不是平衡二叉树
C++ 实现:
//验证平衡二叉树
class Solution
{
public:
int getDepth(TreeNode* node)
{
if (node == NULL)return 0;
int leftDepth = getDepth(node->left);//获取左子树的深度
if (leftDepth == -1)return -1;//判断左子树是不是一颗平衡二叉树
int rightDepth = getDepth(node->right);//获取右子树的深度
if (rightDepth == -1)return -1;//判断右子树是不是一颗平衡二叉树
return abs(leftDepth - rightDepth) > 1 ? -1 : 1 + max(leftDepth, rightDepth);//进行二叉树性质的判定
}
bool isBalanced(TreeNode* root)
{
return getDepth(root) == -1 ? false : true;
}
};