前言
辅助空间★★★★★
1、什么时候使用辅助空间?
利用当前输入数据的数据结构(容器、数组、结构等)不能利用指针等解决问题。需要借助辅助空间对一些数据元素进行特殊操作。
2、辅助空间的作用
当对原容器进行遍历操作时,需要对最近已遍历过元素进行回访,或需要知道已遍历处的特性时,需要标记或创建辅助空间对该特性进行存储。
例一,数组的双指针,遍历利用快慢指针进行回头操作。
例二,栈的特性是先进先出,可以存储元素的最近有利数据,对最近已遍历过元素进行回访十分方便。特别地,单调栈中元素存储具备一致的最近(栈底元素对栈顶元素而言)有效特性。
例三,动态规划中的状态矩阵,利用数组或矩阵记录当前已遍历位置的特性用于后续操作。
一、栈
栈基础知识
1. 语法:
template<class T,class Container = deque<T>> class stack;
T:参数指定容器适配器将保留的元素的类型。
Container:参数指定容器的内部对象,用于容纳堆栈的元素。
栈使用LIFO技术,其中LIFO表示后进先出。首先插入的元素将在末尾提取,以此类推。有一个名为“top”的元素,它是位于最上面位置的元素。所有插入和删除操作都是在堆栈的顶部元素本身进行的。
2. 栈成员类型
成员类型 | 描述 |
---|---|
value_type | 指定了元素类型。 |
container_type | 指定了基础容器类型。 |
size_type | 它指定元素的大小范围。 |
3. 栈相关操作函数
函数 | 描述 |
---|---|
(constructor) | 该函数用于构造堆栈容器。 |
empty | 该函数用于测试堆栈是否为空。如果堆栈为空,则该函数返回true,否则返回false。 |
size | 该函数返回堆栈容器的大小,该大小是堆栈中存储的元素数量的度量。 |
top | 该函数用于访问堆栈的顶部元素。该元素起着非常重要的作用,因为所有插入和删除操作都是在顶部元素上执行的。 |
push | 该函数用于在堆栈顶部插入新元素。 |
pop | 该函数用于删除元素,堆栈中的元素从顶部删除。 |
emplace | 该函数用于在当前顶部元素上方的堆栈中插入新元素。 |
swap | 该函数用于交换引用的两个容器的内容。 |
4. stack相关问答
-
C++中stack 是容器么?
不是容器,栈和队列都是容器适配器。栈和队列是STL(C++标准库)里面的两个数据结构。 -
我们使用的stack是属于哪个版本的STL?
HP STL 其他版本的C++ STL,一般是以HP STL为蓝本实现出来的,HP STL是C++ STL的第一个实现版本,而 且开放源代码。
P.J.Plauger STL 由P.J.Plauger参照HP STL实现出来的,被Visual C++编译器所采用,不是开源的。
SGI STL 由Silicon Graphics Computer Systems公司参照HP STL实现,被Linux的C++编译器GCC所采用。 -
我们使用的STL中stack是如何实现的?
在STL中栈的的默认容器是双端队列 deque,也可以使用 list 和vector 自定义队列,因为 list 和 vector 都提供了删除最后一个元素的操作(出栈)。 -
stack 提供迭代器来遍历stack空间么?
栈提供push 和 pop 等等接口,所有元素必须符合先进后出规则,所以栈不提供走访功能,也不提供迭代器(iterator)。 不像是set 或者map 提供迭代器iterator来遍历所有元素。
栈是以底层容器完成其所有的工作,对外提供统一的接口,底层容器是可插拔的(也就是说我们可以控制使用哪种容器来实现栈的功能)。
普通栈应用算法
232.用栈实现队列 easy
20.有效的括号 easy
1047.删除字符串中的所有相邻重复项 easy
150.逆波兰表达式求值
394.字符串解码
题目:
输入:s = "3[a]2[bc]"
输出:"aaabcbc"
思路:数字存放在数字栈,字符串存放在字符串栈,遇到右括号时候弹出一个数字栈,字母栈弹到左括号为止。就是逆波兰式那种题。
主栈+辅助栈
155.最小栈
题目:设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈。
思路:创建辅助栈存储最小值
单调栈
单调栈是一种数据结构,要求是每次入栈的元素必须要有序(如果新元素入栈不符合要求,则将之前的元素出栈,直到符合要求再入栈),使之形成单调递增/单调递减的一个栈。
- 单调递增栈:只有比栈顶小的才能入栈,否则就把栈顶出栈后,再入栈。出栈时可能会有一些计算。适用于求解第一个大于该位置元素的数。
- 单调递减栈:与单调递增栈相反。适用于求解第一个小于该位置元素的数。
单调递增/递减栈是根据出栈后的顺序来决定的。例如,栈内顺序[1, 2, 6],出栈后顺序[6, 2, 1],这就是单调递减栈。
单调栈(单调增)模板:
vector<int> result (input.size(), -1);
stack<int> monoStack;
for(int i = 0; i < input.size(); ++i) {
while(!monoStack.empty() && input[monoStack.top()] < input[i]) {
result[monoStack.top()] = i - monoStack.top();
monoStack.pop();
}
monoStack.push(i);
}
单调栈的本质是空间换时间,因为在遍历的过程中需要用一个栈来记录右边第一个比当前元素的元素,优点是只需要遍历一次。
下一个更大元素 ★★★★
求取下一个更大的元素,用单调栈(单调增)
739. 每日温度
题目:查找当前元素右边第一个大于的元素,使用单调栈(单调增)
vector<int> dailyTemperatures(vector<int>& input) {
vector<int> result(input.size(), 0);
stack<int>monostack; //单调递增栈(出栈增)
for (int i = 0; i < input.size(); i++) {
//当前栈顶元素与当前input[i]进行比较
while (!monostack.empty() && input[monostack.top()] < input[i]) {
result[monostack.top()] = i - monostack.top();
monostack.pop();
}
monostack.push(i);
}
return result;
}
496.下一个更大元素 I
题目:nums1 是 nums2 的子集,找出 nums1 中每个元素在 nums2 中的下一个比其大的值。
与上题每日温度想法相同,但是在右侧更大元素数据存取上有升级
利用unordered_map<int,int>存储元素值及对应的元素下标,通过元素下标去修改对应的result[]中的值。
503.下一个更大元素II
题目:给定一个循环数组 nums(nums[nums.length - 1] 的下一个元素是 nums[0]),返回 nums 中每个元素的 下一个更大元素 。
循环数组,本质还是同上两题,找寻最大右侧元素。
解决方案,在单调栈(单调增)的基础上遍历两遍数组
vector<int> nextGreaterElements(vector<int>& nums) {
vector<int> result(nums.size(), -1);
stack<int>monostack;
int len = nums.size();
for (int i = 0; i < nums.size() * 2; i++) { //遍历两遍
while (!monostack.empty() && nums[monostack.top()] < nums[i % len])
{
result[monostack.top()] = nums[i % len];
monostack.pop();
}
monostack.push(i % len);
}
return result;
}
42.接雨水 ★★★
给定 n 个非负整数表示每个宽度为 1 的柱子的高度图,计算按此排列的柱子,下雨之后能接多少雨水。
84.柱状图中最大的矩形 ★★★
看到当前遍历元素的高度严格小于栈顶元素高度时,栈顶元素出栈,进而计算出栈顶元素的高度能勾勒出的最大面积。
矩形高度为栈顶元素高度,宽度为当前遍历元素与栈顶元素差值。
int largestRectangleArea(vector<int>& heights) {
stack<int>st;
//取巧,在数组头和尾插入0
heights.insert(heights.begin(), 0);
heights.push_back(0);
//st.push(heights[0]);
int result = 0;
for (int i = 0; i < heights.size(); i++) {
while (!st.empty() && heights[i] < heights[st.top()]) {
//
int curHeight = heights[st.top()];
st.pop();
result = max(result, curHeight * (i - st.top() - 1));
}
st.push(i);
}
return result;
}
85.最大矩形 ★★★
方法一:动态规划的方法(创建左下标、右下标、高度数组)
方法二:单调栈
单调栈的方法从84. 柱状图中最大的矩形演变而来。
1、先使用动态规划计算每一层的柱子的高度
2、单调栈计算每一层的最大矩阵面积(与柱状图中最大矩形一样解法, 必须先理解 84题解)
int maximalRectangle(vector<vector<char>>& matrix) {
//dp[i][j]:i*j范围的最大矩阵面积
int result = 0;
int m = matrix.size(), n = matrix[0].size();
//求取每层的高度柱状图
vector<vector<int>>heights(m + 1, vector<int>(n + 2, 0));
for (int i = 1; i <= m; i++) {
for (int j = 1; j <= n; j++) {
if (matrix[i - 1][j - 1] == '1') heights[i][j] = heights[i - 1][j] + 1;
else heights[i][j] = 0;
}
}
//开始求取逐层的最大面积,同84题
for (int i = 1; i <= m; i++) {
stack<int>st;
for (int j = 0; j <= n + 1; j++) {
while (!st.empty() && heights[i][j] < heights[i][st.top()]) {
int curHeight = heights[i][st.top()];
st.pop();
result = max(result, curHeight * (j - st.top() - 1));
}
st.push(j);
}
}
return result;
}
581.最短无序连续子数组 ★★
使用单调栈有点费空间,直接用双指针+最大值最小值进行操作,巧妙至极
int findUnsortedSubarray(vector<int>& nums) {
int n = nums.size();
//maxn用来记录非升序位置的最大值,right用来记录maxn正确的所在位置
int maxn = INT_MIN, right = -1;
//minn用来记录非升序位置的最小值,left用来记录minn正确的所在位置
int minn = INT_MAX, left = -1;
for (int i = 0; i < n; i++) {
if (maxn > nums[i]) {
right = i;
}
else {
maxn = nums[i];
}
if (minn < nums[n - i - 1]) {
left = n - i - 1;
}
else {
minn = nums[n - i - 1];
}
}
return right == -1 ? 0 : right - left + 1;
}
二、队列
普通队列应用
225.用队列实现栈 easy
单调队列
“如果一个选手比你小还比你强,你就可以退役了。” “如果一个选手比你小还比你强,你就可以退役了。” “如果一个选手比你小还比你强,你就可以退役了。”——单调队列的原理
单调队列的基本思想是,维护一个双向队列(deque),遍历序列,仅当一个元素可能成为某个区间最值时才保留它。
deque<int> Q; // 存储的是编号
for (int i = 0; i < n; ++i)
{
if (!Q.empty() && i - Q.front() >= m) // 毕业
Q.pop_front();
while (!Q.empty() && V[Q.back()] < V[i]) // 比新生弱的当场退役(求区间最小值把这里改成>即可)
Q.pop_back();
Q.push_back(i); // 新生入队
if (i >= m - 1)
cout << V[Q.front()] << " ";
}
239.滑动窗口最大值
题目:给定一个数组 nums 和滑动窗口的大小 k,请找出所有滑动窗口里的最大值。
实例:
输入:nums = [1,3,-1,-3,5,3,6,7], k = 3
输出:[3,3,5,5,6,7]
class MyQueue { //单调队列(从大到小)
public:
deque<int> que; // 使用deque来实现单调队列
// 每次弹出的时候,比较当前要弹出的数值是否等于队列出口元素的数值,如果相等则弹出。
// 同时pop之前判断队列当前是否为空。
void pop(int value) {
if (!que.empty() && value == que.front()) {
que.pop_front();
}
}
// 如果push的数值大于入口元素的数值,那么就将队列后端的数值弹出,直到push的数值小于等于队列入口元素的数值为止。
// 这样就保持了队列里的数值是单调从大到小的了。
void push(int value) {
while (!que.empty() && value > que.back()) {
que.pop_back();
}
que.push_back(value);
}
// 查询当前队列里的最大值 直接返回队列前端也就是front就可以了。
int front() {
return que.front();
}
};
优先级队列 priority_queue
优先级队列虽然也叫队列,但是和普通的队列还是有差别的。普通队列出队顺序只取决于入队顺序,而优先级队列的出队顺序总是按照元素自身的优先级。换句话说,优先级队列是一个自动排序的队列。元素自身的优先级可以根据入队时间,也可以根据其他因素来确定,因此非常灵活。
模板类型:
template <typename T, typename Container=std::vector<T>, typename Compare=std::less<T>> class priority_queue
容器选择:
优先级队列可以使用任何容器来保存元素,只要容器有成员函数 front()、push_back()、pop_back()、size()、empty()。这显然包含了 deque 容器,因此这里也可以用 deque 来代替:
std::string wrds [] {"one", "two", "three", "four"};
std::priority_queue<std::string, std::deque<std::string>> words {std::begin(wrds), std::end(wrds)};
这个 words 优先级队列在 deque 容器中保存了一些 wrds 数组中的字符串,这里使用默认的比较断言,因此队列中的元素会和上面 word1 中元素的顺序相同。priority_queue 构造函数会生成一个和第二个类型参数同类型的容器来保存元素,这也是 priority_queue 对象的底层容器。
1.定义
priority_queue<Type, Container, Functional>;
Type是要存放的数据类型
Container是实现底层堆的容器,必须是数组实现的容器,如vector、deque
Functional是比较方式/比较函数/优先级
priority_queue;
此时默认的容器是vector,默认的比较方式是大顶堆less
举例
//小顶堆
priority_queue <int,vector<int>,greater<int> > q;
//大顶堆
priority_queue <int,vector<int>,less<int> >q;
//默认大顶堆
priority_queue<int> a;
//pair
priority_queue<pair<int, int> > a;
pair<int, int> b(1, 2);
pair<int, int> c(1, 3);
pair<int, int> d(2, 5);
a.push(d);
a.push(c);
a.push(b);
while (!a.empty())
{
cout << a.top().first << ' ' << a.top().second << '\n';
a.pop();
}
//输出结果为:
2 5
1 3
1 2
2.常用函数
top()
pop()
push()
emplace()
empty()
size()
3.自定义比较方式
当数据类型并不是基本数据类型,而是自定义的数据类型时,就不能用greater或less的比较方式了,而是需要自定义比较方式
在此假设数据类型是自定义的水果:
struct fruit
{
string name;
int price;
};
有两种自定义比较方式的方法,如下
3.1 重载运算符
重载”<”
若希望水果价格高为优先级高,则
//大顶堆
struct fruit
{
string name;
int price;
friend bool operator < (fruit f1,fruit f2)
{
return f1.peice < f2.price;
}
};
若希望水果价格低为优先级高
//小顶堆
struct fruit
{
string name;
int price;
friend bool operator < (fruit f1,fruit f2)
{
return f1.peice > f2.price; //此处是>
}
};
3.2 仿函数
若希望水果价格高为优先级高,则若希望水果价格高为优先级高,则
//大顶堆
struct myComparison
{
bool operator () (fruit f1,fruit f2)
{
return f1.price < f2.price;
}
};
//此时优先队列的定义应该如下
priority_queue<fruit,vector<fruit>,myComparison> q;
347.前K个高频元素★★★★★
- 统计元素出现的次数
- 对元素出现的频率进行排序
- 选取前k个元素
三、前缀树
Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。
208.实现Trie(前缀树)
720.词典中最长的单词
692.前K个高频单词
法一:哈希表+排序
法二:优先级队列★★★
法三:前缀树
C++中Trie需要手写!
class Trie {
public:
Trie():children(26),isEnd(false) {}
void insert(string word) {
Trie* node = this;
for(char ch : word){
ch -= 'a';
if(node->children[ch]==nullptr){
node->children[ch] = new Trie();
}
node = node->children[ch];
}
node->isEnd = true;
}
bool search(string word) {
Trie* node = this->searchPrefix(word);
return node!= nullptr && node->isEnd;
}
bool startsWith(string prefix) {
return this->searchPrefix(prefix) != nullptr;
}
private:
//指向子节点的指针数组
vector<Trie*> children;
//判断该节点是否为字符串的结尾
bool isEnd;
//查找前缀
Trie* searchPrefix(string prefix){
Trie* node = this;
for(char ch:prefix){
ch -= 'a';
if(node->children[ch]==nullptr){
return nullptr;
}
node = node->children[ch];
}
return node;
}
};
四、并查集
并查集(Union-find Sets)是一种非常精巧而实用的数据结构,主要用于处理一些不相交集合的合并问题。
一些常见的用途有:求连通子图、求最小生成树的Kruskal算法和求最近公共祖先(LCA)等有衍生上下级关系的结构。
并查集的基本操作主要有:
1.初始化init
2.查询find
3.合并union
1.初始化
int father[n];//存储父节点
void init(int n){
for(int i=0;i<n;++i){
father[i]=i;//初始将父节点设为自己
}
}
2.查询
找到i的祖先直接返回,未找到的进行路径压缩
int find(int i){
if(father[i]==i)//递归出口,当到达了祖先位置,就返回祖先
return i;
else
return find(father[i]);//不断递归查找祖先
}
3.合并
void union(int i ,int j){
int i_fa = find(i);//找到i的祖先
int j_fa = find(j);//找到j的祖先
if(i_fa!=j_fa) father[i_fa] = j_fa;//i的祖先指向j的祖先
}
汇总UnionFind类模板
class UnionFind
{
private:
int father[n];//存储父节点
public:
void init(int n){
for(int i=0;i<n;++i){
father[i]=i;//初始将父节点设为自己
}
}
int find(int i){
if(father[i]==i)//递归出口,当到达了祖先位置,就返回祖先
return i;
else
return find(father[i]);//不断递归查找祖先
}
void union(int i ,int j){
int i_fa = find(i);//找到i的祖先
int j_fa = find(j);//找到j的祖先
if(i_fa!=j_fa) father[i_fa] = j_fa;//i的祖先指向j的祖先
}
};
200.岛屿数量 ★★★★★【字节、美团、微软、网易互娱】
五、二叉树
1、二叉树基础:
- 满二叉树
- 完全二叉树
- 二叉搜索树
- 平衡二叉树
- 平衡二叉搜索树;map、set 、multimap、multiset的底层都是平衡二叉搜索树
2、二叉树的遍历方式
- 深度优先
- 广度优先
二叉树的最大深度:
二叉树的最小深度:
在二叉树中,前序求的是深度,后序求的是高度
层序遍历来求深度,但是就不能直接用层序遍历来求高度
完全二叉树节点的个数:
二叉树递归总结:
1.需要搜索整个二叉树+不用处理返回值 = 递归函数不用返回值;
2.搜索整个二叉树+处理返回值 = 需要返回值;
3.搜索二叉树中一条符合条件的路径 = 需要返回值;
前序遍历
257.二叉树的所有路径
记录从根节点出发的所有路径,经典前序遍历,中左右
112路径总和
注意:
- 记录从根节点到叶子节点的单条路径信息,用前序遍历;
算法:
- 递归-前序遍历:中(处理节点) 左右(遍历节点);返回值设置为bool值
- 迭代-前序遍历:栈中需要记录节点指针+到节点的总和(pair<节点指针, 路径数值> )
//处理节点为叶子节点 && 当前节点的路径总和为target return true;
//左节点
//右节点
654最大二叉树 构造二叉树
数组构建二叉树,用递归遍历-前序
108.将有序数组转换为二叉搜索树
思路:同上,构建二叉树
617.合并二叉树 easy
题目:合并到其中一颗树上;两节点结合,一个节点为空,用另一节点状态表示合并状态
思路:递归-前中后序都可以,不涉及子树,只是两节点的处理
235.二叉搜索树的最近公共祖先
二叉搜索树可以利用当前节点的大小进行比较,前序遍历从上到下即可。
669.修剪二叉搜索树 ★★
思路:前序遍历,但是处理节点的时候依旧是要递归处理
TreeNode* trimBST(TreeNode* root, int low, int high) {
//前序遍历
if(root==nullptr) return root;
//处理节点
if(root->val<low){
TreeNode* right = trimBST(root->right, low, high); // 寻找符合区间[low, high]的节点
return right;
}
if(root->val>high){
TreeNode* left = trimBST(root->left, low, high); // 寻找符合区间[low, high]的节点
return left;
}
//遍历节点
root->left = trimBST(root->left,low,high);
root->right = trimBST(root->right , low ,high);
return root;
}
中序遍历
98验证二叉搜索树
思路:二叉搜索树的中序遍历是单调的,用当前最大值val中序遍历节点,判断整个树是否为递增的val值即可进行判断。
误区:不能单纯比较左右节点和中间节点的大小关系,因为二叉搜索树需要整个树单调。
class Solution {
public:
long long min_val = LONG_MIN;
bool isValidBST(TreeNode* root) {
//中序遍历
if(root==nullptr) return true;
//遍历节点
bool left = isValidBST(root->left);
//处理节点
if(root->val <= min_val) return false;
else min_val = root->val;
//遍历节点
bool right = isValidBST(root->right);
return left && right;
}
};
530二叉搜索树的最小绝对差
思路:二叉搜索树用中序遍历,创建TreeNode * pre记录前节点,创建int result记录最小绝对差,用result与当前节点-前节点进行比较。
501二叉搜索树中的众数
思路:中序遍历中对中间节点进行统计+处理
算法:1、创建前节点pre;2、创建当前频率次数记录count;3、创建最高频率次数记录max_count;
如果不是二叉搜索树
用unordered_map<int , int>进行存储记录数据
对map中的value排序(std::map或者std::multimap可以对key排序,但不能对value排序),需要将map转成vector进行排序
bool static cmp (const pair<int, int>& a, const pair<int, int>& b) {//用static是因为静态函数只对声明它的文件可见
return a.second > b.second;
}
vector<pair<int, int>> vec(map.begin(), map.end());
sort(vec.begin(), vec.end(), cmp); // 给频率排个序
后序遍历
226.翻转二叉树 easy
101.对称二叉树 非传统后序
这里的后序遍历是:先遍历节点,后处理节点。
- 比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
- 比较内测是否对称,传入左节点的右孩子,右节点的左孩子。
- 如果左右都对称就返回true ,有一侧不对称就返回false 。
104.二叉树的最大深度
本题可以使用前序(中左右),也可以使用后序遍历(左右中),使用前序求的就是深度,使用后序求的是高度。
111.二叉树的最小深度
222.完全二叉树的节点个数
法一:普通二叉树节点个数计算:后序遍历(递归)
时间复杂度:O(n)
空间复杂度:O(log n),算上了递归系统栈占用的空间
int getNodesNum(TreeNode* cur) {
if (cur == NULL) return 0;
int leftNum = getNodesNum(cur->left); // 左
int rightNum = getNodesNum(cur->right); // 右
int treeNum = leftNum + rightNum + 1; // 中
return treeNum;
}
法二:普通二叉树节点计算:层序遍历
时间复杂度:O(n)
空间复杂度:O(n)
法三:抓住完全二叉树的特性
如果整个树不是满二叉树,就递归其左右孩子,直到遇到满二叉树为止,用公式计算这个子树(满二叉树)的节点数量。
时间复杂度:O(log n × log n)
空间复杂度:O(log n)
int countNodes(TreeNode* root) {
if (root == nullptr) return 0;
TreeNode* left = root->left;
TreeNode* right = root->right;
int leftHeight = 0, rightHeight = 0; // 这里初始为0是有目的的,为了下面求指数方便
while (left) { // 求左子树深度
left = left->left;
leftHeight++;
}
while (right) { // 求右子树深度
right = right->right;
rightHeight++;
}
if (leftHeight == rightHeight) {
return (2 << leftHeight) - 1; // 注意(2<<1) 相当于2^2,所以leftHeight初始为0
}
return countNodes(root->left) + countNodes(root->right) + 1;
}
110.平衡二叉树
平衡二叉树:非叶子节点的左右子树高度相差不大于1。
判断是否是平衡二叉树,肯定是后序遍历,先遍历节点再处理节点并返回。
求取平衡二叉树就是后序遍历左右子树的高度,对于不合格的用-1标记。
404左叶子之和
注意:
- 左叶子不是层序遍历第一个左侧叶子;
- 左叶子节点无法用当前节点来进行判断,当前节点只能判断是叶子节点
- 左叶子定义:当前节点的左叶子节点非空+左叶子节点的左节点为空+左叶子节点的右节点为空 if(root->left!=nullptr && root->left->left== nullptr && root->left->right== nullptr);
算法:
1.递归:后序遍历(左右中)。因为要通过递归函数的返回值累加求取总和(左右节点的总值求出后返回给中间节点进行递归)
2.迭代(easy):前中后遍历都行。
236二叉树的最近公共祖先 ★★★★★
题目:给定二叉树中的两节点,找到公共祖先。
思路:后序遍历,
返回值:当前节点或nullptr
终止条件:对当前节点进行分析;
处理节点:对当前节点左右值进行分析,注意,只有左右值中都存在节点才可能是公共祖先的情况之一。
对左右子树的返回值进行判断
TreeNode* lowestCommonAncestor(TreeNode* root, TreeNode* p, TreeNode* q) {
//后序遍历-找出指定两个节点的祖先
if (root == nullptr || root == p || root == q) return root;//重点:两节点一定存在
//遍历节点
TreeNode* left = lowestCommonAncestor(root->left, p, q);
TreeNode* right = lowestCommonAncestor(root->right, p, q);
//处理节点
if (left != nullptr && right != nullptr) return root;//找到公共祖先
else if (left == nullptr && right != nullptr) return right;
else if (left != nullptr && right == nullptr) return left;
else return nullptr;
}
701.二叉搜索树中的插入操作
TreeNode* insertIntoBST(TreeNode* root, int val) {
//后序遍历
//终止条件
if (root == nullptr) {
TreeNode* node = new TreeNode(val);
return node;
}
//遍历节点
if (root->val > val) root->left = insertIntoBST(root->left, val);
else if (root->val < val) root->right = insertIntoBST(root->right, val);
return root;
}
层序遍历
513找树的左下角的值
注意:
- 最后一行的最左边的值;
- 找最左边节点,前序遍历就行;
算法:
- 递归-前序:1、深度最大的叶子节点是最后一行;2、用前序遍历的特性记录最左边;3、记录最大深度
- 迭代-层序(easy):记录每行第一个节点都行。
113路径总和II
注意:
求总和时候定好当前节点的值在不在sum中,有没有被减去
回溯针对存储了路径的数组 和 带入的sum值
106.从中序与后序遍历序列构造二叉树
注意:
- 后序遍历最后一个节点是当前二叉树的根节点;
- 中序遍历:前 中 后
- 递归返回节点指针
算法:
- 递归-前序:1、设立中间节点;2、建立左子树,递归带入左子树的中后遍历数组;3、建立右子树,递归带入右子树的中后遍历数组;