动态规划后的刷题,感觉需要加快刷题的速度了,不过在加快速度的同时不能放松质量每天一点每天一点
参考代码随想录代码随想录 (programmercarl.com)
平凡的世界里的一句话--要想求得解放,就要舍身于劳动。
二叉树基础
有序树:如果树中结点的子树从左到右,谁在左边谁在右边是有规定的
二叉树:1、本身是有序树;2、树包含的各个结点的度为0、1或者2
二叉树有两种主要的形式:满二叉树和完全二叉树
度:结点的分支数
满二叉树:一颗二叉树只有度为0和2的的结点,并且度为0的结点在同一层上。
满二叉树的深度为k的话,结点数为2^k-1
完全二叉树:除了最底层节点可能没填平外,其余每层结点数都达到最大值,并且最下面一层的结点都集中在该层最左边的若干位置。
二叉搜索树:有数值的有序树
若它的左子树不为空,则左子树上所有结点的值均小于它根节点的值;
若它的右子树不为空,则右子树上所有结点的值均大于它根节点的值;
左右子树也分别为二叉搜索树。
平衡二叉搜索树:
是空树或者它的左右子树的高度差的绝对值不超过1;
左右子树都是一颗平衡二叉树。
C++中map、set、multimap、multiset的底层实现都是平衡二叉搜索树;时间复杂度为log n
而unordered_set和unordered_map的底层实现都是哈希表
二叉树的存储
链式存储(使用指针)和顺序存储(使用数组)
一般采用链式存储
顺序存储的元素在内存是连续分布的,而链式存储则是通过指针把分布在各个地址的节点串联一起
顺序存储
数组存储二叉树遍历:
如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2
二叉树的遍历方式
1、深度优先遍历:先往深走,遇到叶子节点再往回走
前序遍历(递归法、迭代法)
中序遍历(递归法、迭代法)
后序遍历(递归法、迭代法)
2、广度优先遍历:一层一层的去遍历
层序遍历(迭代法)
遍历顺序(多使用递归法)
栈其实就是递归的一种实现结构,也就说前中后序遍历的逻辑其实都是可以借助栈使用非递归的方式来实现的。
而广度优先遍历的实现一般使用队列来实现,这也是队列先进先出的特点所决定的,因为需要先进先出的结构,才能一层一层的来遍历二叉树。
二叉树的定义
struct TreeNode{
TreeNode * left;
TreeNode * right;
int val;
TreeNode():val(0), left(nullptr), right(nullptr){}
TreeNode(int x):val(x), left(NULL), right(NULL){}
TreeNode(int x, TreeNode * left, TreeNode * right):val(x), left(left), right(right){}
}
二叉树的递归遍历
递归三要素:
1、明确递归的参数和返回值
2、确定终止条件
操作系统也是用一个栈的结构保存每层递归信息,如果没有终止条件必定出现栈溢出
3、确定单层递归的逻辑
前序遍历
使用递归三要素:
1、明确递归的参数和返回值
无需返回值,参数是当前结点以及存放遍历结点的数组
TreeNode * Node, vector<int> & vec
2、确定终止条件
if(Node == NULL) return;
3、确定单层递归的逻辑
前序遍历就是中左右
vec.push_back(cur->val);中
traversal(cur->left, vec);左
traversal(cur->right, vec);右
代码
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> result;
traversal(root, result);
return result;
}
};
中序遍历
相比较前序遍历就是:左中右
class Solution {
public:
void hanshu(TreeNode* cur, vector<int> &vec) {
if (cur == nullptr) {
return;
}
hanshu(cur->left, vec);
vec.push_back(cur->val);
hanshu(cur->right,vec);
}
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
hanshu(root, result);
return result;
}
};
后序遍历
相比较前序遍历就是:左右中
class Solution {
public:
void hanshu(TreeNode* cur, vector<int> &vec) {
if (cur == nullptr) {
return;
}
hanshu(cur->left, vec);
hanshu(cur->right, vec);
vec.push_back(cur->val);
}
vector<int> postorderTraversal(TreeNode* root) {
vector<int> result;
hanshu(root, result);
return result;
}
};
二叉树的迭代遍历
递归的实现就是:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中(递归的实现是虚拟空间中的栈),然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因
使用栈来实现二叉树的迭代遍历,栈的特点是先进后出
st.push(root)要区别于vector的result.push_back(val)
st.top()是stack的头
st.empty()判断栈是否为空
stack<TreeNode *> st 储存结点
前序遍历
先存中,接着pop,接着存右结点,接着存左节点
class Solution {
public:
vector<int> preorderTraversal(TreeNode* root) {
stack<TreeNode *> st;
vector<int> result;
if(root == NULL) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
result.push_back(node->val);
if(node->right) st.push(node->right); //right不能为空
if(node->left) st.push(node->left); //left不能为空
}
return result;
}
};
中序遍历
不能像递归一样由前序遍历转换一下顺序就变为了中序遍历
前序遍历的迭代法是要访问的元素和要处理的元素顺序是一致的,都是中间结点。所以代码简洁
中序遍历是左中右,先访问的是二叉树顶部的结点,然后一层一层向下访问,直到到达树左面的最底部,再把节点的数值放进result数组中
在使用迭代法写中序遍历,需借用指针的遍历来帮助访问节点,栈则用来处理节点上的元素
代码
class Solution {
public:
vector<int> inorderTraversal(TreeNode* root) {
vector<int> result;
stack<TreeNode*> st;
TreeNode* cur = root;
while (cur != nullptr || !st.empty()) {
if (cur != nullptr) {
st.push(cur);
cur = cur->left;//左
}else{
cur = st.top();
st.pop();
result.push_back(cur->val);//中
cur = cur->right;//右
}
}
return result;
}
};
后序遍历
class Solution {
public:
vector<int> postorderTraversal(TreeNode* root) {
stack<TreeNode*> st;
vector<int> result;
if (root == NULL) return result;
st.push(root);
while (!st.empty()) {
TreeNode* node = st.top();
st.pop();
result.push_back(node->val);
if (node->left) st.push(node->left); // 相对于前序遍历,这更改一下入栈顺序 (空节点不入栈)
if (node->right) st.push(node->right); // 空节点不入栈
}
reverse(result.begin(), result.end()); // 将结果反转之后就是左右中的顺序了
return result;
}
};
二叉树的层序遍历
二叉树的层序遍历
需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑
这种层序遍历方式就是图论中的广度优先遍历,只不过我们应用在二叉树上
二叉树的层序遍历
递归法:
if(result.size() == depth) result.push_back(vector<int>()); 相当于分隔符了,往二维数组里插入空的vector<int>(),可以理解为分行,即二维数组的下一行
result[depth].push_back(cur->val);相当于在每一行插入数据
order(cur->left, result, depth+1);
order(cur->right, result, depth+1);递归,相当于在下一行处理数据,由于地柜最终会插入到同一行
【C++笔记】关于push_back(vector<int>());_ans.push_back_Linxson的博客-CSDN博客
class Solution {
public:
void order (TreeNode* cur, vector<vector<int>>& result, int depth){
if(cur == nullptr) return;
if(result.size() == depth) result.push_back(vector<int>());
result[depth].push_back(cur->val);
order(cur->left, result, depth+1);
order(cur->right, result, depth+1);
}
vector<vector<int>> levelOrder(TreeNode* root) {
vector<vector<int>> result;
int depth = 0;
order(root, result, depth);
return result;
}
};
二叉树的层序遍历II
107. 二叉树的层序遍历 II - 力扣(Leetcode)
就是翻转一下
class Solution {
public:
void order (TreeNode* cur, vector<vector<int>>& result, int depth){
if(cur == nullptr) return;
if(result.size() == depth) result.push_back(vector<int>());
result[depth].push_back(cur->val);
order(cur->left, result, depth+1);
order(cur->right, result, depth+1);
}
vector<vector<int>> levelOrderBottom(TreeNode* root) {
vector<vector<int>> result;
int depth = 0;
order(root, result, depth);
reverse(result.begin(), result.end());
return result;
}
};
翻转二叉树
递归法
使用递归法翻转,三步骤:
1、确定返回值、参数
2、返回条件
3、单层递归的逻辑
前序遍历
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
swap(root->left, root->right); // 中
invertTree(root->left); // 左
invertTree(root->right); // 右
return root;
}
};
迭代法
class Solution {
public:
TreeNode* invertTree(TreeNode* root) {
if (root == NULL) return root;
stack<TreeNode*> st;
st.push(root);
while(!st.empty()) {
TreeNode* node = st.top();
st.pop();
swap(node->left, node->right);
if(node->right) st.push(node->right);
if(node->left) st.push(node->left);
}
return root;
}
};
对称二叉树
递归法
确定递归的终止条件:
if(left == nullptr && right != nullptr) return false;
else if(left != nullptr && right == nullptr) return false;
else if(left == nullptr && right == nullptr) return true;
else if(left->val != right->val) return false;
递归逻辑:
主要是要考虑里和外
代码
class Solution {
public:
bool compare(TreeNode* left, TreeNode* right) {
if(left == nullptr && right != nullptr) return false;
else if(left != nullptr && right == nullptr) return false;
else if(left == nullptr && right == nullptr) return true;
else if(left->val != right->val) return false;
bool outside = compare(left->left, right->right); // 左子树:左、 右子树:右
bool inside = compare(left->right, right->left); // 左子树:右、 右子树:左
bool isSame = outside && inside; // 左子树:中、 右子树:中(逻辑处理)
return isSame;
}
bool isSymmetric(TreeNode* root) {
if (root == nullptr) return true;
else {
return compare(root->left, root->right);
}
}
};
二叉树的最大深度
使用前序求深度,使用后序求高度。
本题的根节点的高度就是最大深度,可以使用后序遍历
求最大深度利用后序遍历,要知道二叉树有四种遍历方法,而每种遍历方法又有递归和迭代
直接上代码:
class Solution {
public:
int getdepth(TreeNode* cur) {
if(cur == nullptr) return 0;
int leftdepth = getdepth(cur->left);
int rightdepth = getdepth(cur->right);
int depth = 1 + max(leftdepth, rightdepth);
return depth;
}
int maxDepth(TreeNode* root) {
return getdepth(root);
}
};
二叉树的最小深度
这个题主要是:
如果左子树为空,右子树不为空,说明最小深度是 1 + 右子树的深度。
反之,右子树为空,左子树不为空,最小深度是 1 + 左子树的深度。 最后如果左右子树都不为空,返回左右子树深度最小值 + 1 。
代码
class Solution {
public:
int depth(TreeNode* cur) {
if (cur == nullptr) return 0;
int leftdepth = depth(cur->left);
int rightdepth = depth(cur->right);
if (cur->left == nullptr && cur->right != nullptr) return 1 + rightdepth;
if (cur->left != nullptr && cur->right == nullptr) return 1 + leftdepth;
int result = 1 + min(rightdepth, leftdepth);
return result;
}
int minDepth(TreeNode* root) {
return depth(root);
}
};
完全二叉树的节点个数
222. 完全二叉树的节点个数 - 力扣(Leetcode)
这个题的递归法和二叉树求深度写法类似
依旧采用后序遍历
代码
class Solution {
public:
int getcountNodes(TreeNode* cur) {
if (cur == nullptr) return 0;
int leftNum = getcountNodes(cur->left);
int rightNum = getcountNodes(cur->right);
int countNum = 1 + leftNum + rightNum;
return countNum;
}
int countNodes(TreeNode* root) {
return getcountNodes(root);
}
};
平衡二叉树
这个题其实感觉理解上有些困难
代码
class Solution {
public:
int getHeight(TreeNode* cur) {
if(cur == nullptr) return 0;
int leftHeight = getHeight(cur->left);
if (leftHeight == -1) return -1;
int rightHeight = getHeight(cur->right);
if (rightHeight == -1) return -1;
return abs(leftHeight - rightHeight) > 1 ? -1 : 1 + max(leftHeight, rightHeight);
}
bool isBalanced(TreeNode* root) {
return getHeight(root) == -1 ? false : true;
}
};
二叉树的所有路径
这个题使用前序遍历,因为只有前序遍历能够指向下一个节点,同时需要使用回溯的方法:因为需要一个数组容器储存路径,那个如果找到了一个路径了之后,之后找下一个路径时候需要将上一次路径的节点弹出,如何弹出就要使用回溯。
1、递归函数参数
需要一个节点,一个int数组储存单条路径,一个string数组储存最终结果的所有路径。
2、递归终止条件
左右孩子都为空,即为遍历到叶子节点,从而终止,此时将int数组储存到string数组中(int类型转成string类型,同时加上一个->)。
3、递归
回溯和递归是一一对应的,有一个递归,就要有一个回溯
class Solution {
private:
void traversal(TreeNode* cur, vector<int>& path, vector<string>& result) {
path.push_back(cur->val); // 中,中为什么写在这里,因为最后一个节点也要加入到path中
// 这才到了叶子节点
if (cur->left == NULL && cur->right == NULL) {
string sPath;
for (int i = 0; i < path.size() - 1; i++) {
sPath += to_string(path[i]);
sPath += "->";
}
sPath += to_string(path[path.size() - 1]);
result.push_back(sPath);
return;
}
if (cur->left) { // 左
traversal(cur->left, path, result);
path.pop_back(); // 回溯
}
if (cur->right) { // 右
traversal(cur->right, path, result);
path.pop_back(); // 回溯
}
}
public:
vector<string> binaryTreePaths(TreeNode* root) {
vector<string> result;
vector<int> path;
if (root == NULL) return result;
traversal(root, path, result);
return result;
}
};
左叶子之和
如果该节点的左节点不为空,该节点的左节点的左节点为空,该节点的左节点的右节点为空,则找到了一个左叶子
class Solution {
public:
int sumOfLeftLeaves(TreeNode* root) {
if (root == NULL) return 0;
if (root->left == NULL && root->right== NULL) return 0;
int leftValue = sumOfLeftLeaves(root->left); // 左
if (root->left && !root->left->left && !root->left->right) { // 左子树就是一个左叶子的情况
leftValue = root->left->val;
}
int rightValue = sumOfLeftLeaves(root->right); // 右
int sum = leftValue + rightValue; // 中
return sum;
}
};