系列综述:
💞目的:本系列是个人整理为了秋招算法
的,整理期间苛求每个知识点,平衡理解简易度与深入程度。
🥰来源:材料主要源于代码随想录
进行的,每个算法代码参考leetcode高赞回答和其他平台热门博客,其中也可能含有一些的个人思考。
🤭结语:如果有帮到你的地方,就点个赞和关注一下呗,谢谢🎈🎄🌷!!!
🌈数据结构基础知识总结篇
文章目录
- 一、二叉树理论基础
- 定义
- 二叉树深度优先遍历
- 二叉树广度优先遍历
- 二叉树最大深度
- 二叉树最小深度
- 完全二叉树
- 判断是否为平衡二叉树
- 二、相关题目
- 翻转二叉树
- 二叉树是否对称
- 判断是否为平衡二叉树
- 二叉树的所有路径
- 左叶子之和
- 求二叉树最左下的叶子
- 符合总和的路径
- 106. 从中序与后序遍历序列构造二叉树
- 654. 最大二叉树
- 654. 最大二叉树
- 参考博客
😊点此到文末惊喜↩︎
一、二叉树理论基础
定义
- 二叉树数据结构
struct TreeNode { int val; TreeNode *left; TreeNode *right; TreeNode(int x) : val(x), left(NULL), right(NULL) {} };
二叉树深度优先遍历
- 递归式
// 前序遍历 void traversal(TreeNode* cur, vector<int>& vec) { if (cur == NULL) return; vec.push_back(cur->val); // 中 traversal(cur->left, vec); // 左 traversal(cur->right, vec); // 右 } // 中序遍历 void traversal(TreeNode* cur, vector<int>& vec) { if (cur == NULL) return; traversal(cur->left, vec); // 左 vec.push_back(cur->val); // 中 traversal(cur->right, vec); // 右 } // 后序遍历 void traversal(TreeNode* cur, vector<int>& vec) { if (cur == NULL) return; traversal(cur->left, vec); // 左 vec.push_back(cur->val); // 中 traversal(cur->right, vec); // 右 } // 调用示例 vector<int> Traversal(TreeNode* root) { vector<int> result; traversal(root, result); return result; }
- 非递归:将前序、中序和后序统一化处理,将遍历核心顺序进行
逆序转化
- 算法遍历部分的逆序
- 对于值节点的处理
vector<int> postorderTraversal(TreeNode* root) { // 初始化 vector<int> result; stack<TreeNode*> st; if (root != NULL) // 压入根节点 st.push(root); // 遍历源容器 while (!st.empty()) { TreeNode* node = st.top(); if (node != NULL) { st.pop(); // 算法变化的部分,遍历的逆序 // 中 st.push(node); st.push(NULL); // 右 if (node->right) st.push(node->right); // 左 if (node->left) st.push(node->left); } else { // 对值节点的处理 st.pop();// 弹出空结点 node = st.top(); st.pop(); result.push_back(node->val); } } return result; }
二叉树广度优先遍历
- 递归法
// 递归参数,如果需要修改要进行引用传递 void traversal(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; // 递归调用 traversal(root, result, depth); // 返回结果 return result; }
- 非递归法
vector<vector<int>> levelOrder(TreeNode* root) { // 初始化 queue<TreeNode*> que; vector<vector<int>> result; if(root != nullptr) que.push(root);// 将声明队列并将根节点压入栈中 // 迭代 while (!que.empty()) { int size = que.size();// 记录每层要遍历的根节点数量 TreeNode* node;// vector<int> vec;// 层次遍历要筛选的元素 // 不要使用que.size(),因为que.size在循环体内不断变化 for (int i = 0; i < size; i++) { // 队首结点放到结构队列中 node = que.front(); vec.push_back(node->val); // 弹出队首节点,并将它的孩子结点按序压入栈中 que.pop(); if (node->left) que.push(node->left); if (node->right) que.push(node->right); } // 将每层筛选元素压入结果数组中 result.push_back(vec); } // 输出 return result; }
二叉树最大深度
- 递归法
// 递归只考虑当前层,不要过于考虑整体 int depth(TreeNode* root) { // 1. 如果当前 root 为 null,说明当前层的深度就是 0 if (!root) { return 0; } // 2. 分别计算左子树和右子树的深度 int L = depth(root->left); int R = depth(root->right); // 3. 获取当前树的左子树和右子树深度的较大值,加 1 (本层深度) return max(L,R) + 1; } // 简略版 int depth(TreeNode* cur) { //计算最大深度 return (cur == nullptr) ? 0 : max(depth(cur->left), depth(cur->right)) + 1; }
- 非递归法
int maxDepth(TreeNode* root) { queue<TreeNode*> q; if(root) q.push(root); int depth = 0; while(!q.empty()){ int size = q.size(); ++depth; for(int i = 0; i < size; ++i){ TreeNode *cur = q.front(); q.pop(); if(cur->left) q.push(cur->left); if(cur->right) q.push(cur->right); } } return depth; }
二叉树最小深度
- 递归法
- 二叉树的五种形态
- 空二叉树
- 只有根节点
- 只有左子树
- 只有右子树
- 左右子树都有
int minDepth(TreeNode* root) { // 空二叉树 if (root == NULL) return 0; // 只有左子树 if (root->left != NULL && root->right == NULL) { return 1 + minDepth(root->left); } // 只有右子树 if (root->left == NULL && root->right != NULL) { return 1 + minDepth(root->right); } // 左右子树都非空 return 1 + min(minDepth(root->left), minDepth(root->right)); }
- 二叉树的五种形态
- 非递归法
- 找到第一个左右孩子均为空的,即为最小深度
int minDepth(TreeNode* root) { if (root == NULL) return 0; int depth = 0; queue<TreeNode*> que; que.push(root); while(!que.empty()) { int size = que.size(); depth++; // 记录最小深度 for (int i = 0; i < size; i++) { TreeNode* node = que.front(); que.pop(); if (node->left) que.push(node->left); if (node->right) que.push(node->right); if (!node->left && !node->right) { // 第一个左右孩子均空,为最小深度 return depth; } } } return depth; }
完全二叉树
- 递归法
- 递归法要只考虑单层的逻辑
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; }
- 非递归法
- 找到第一个左右孩子均为空的,即为最小深度
int countNodes(TreeNode* root) { queue<TreeNode*> que; if (root != NULL) que.push(root); int result = 0; while (!que.empty()) { int size = que.size(); for (int i = 0; i < size; i++) { TreeNode* node = que.front(); que.pop(); result++; // 记录节点数量 if (node->left) que.push(node->left); if (node->right) que.push(node->right); } } return result; }
判断是否为平衡二叉树
- 递归法
- 递归法要只考虑单层的逻辑
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; }
- 非递归法
- -1是剪枝的标志位,即如果不是平衡二叉树,
int getHeight(TreeNode* node) { if (node == NULL) { return 0; } int leftHeight = getHeight(node->left); if (leftHeight == -1) return -1; int rightHeight = getHeight(node->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; }
二、相关题目
翻转二叉树
- 翻转二叉树
- 对于二叉树的操作都是从二叉树的遍历衍生出来的
// 前序遍历 void traversal(TreeNode *cur){ if(cur == nullptr) return ; swap(cur->left, cur->right); if(cur->left) traversal(cur->left); if(cur->right) traversal(cur->right); } // 调用函数 TreeNode* invertTree(TreeNode* root) { if(root == nullptr) return nullptr; traversal(root); return root; }
- 对于二叉树的操作都是从二叉树的遍历衍生出来的
二叉树是否对称
-
101. 对称二叉树
- 对称二叉树要比较的不是左右节点,而是根节点的左子树和右子树是否能反转
bool compare(TreeNode* left, TreeNode* right) { if (left == NULL && right != NULL) return false; else if (left != NULL && right == NULL) return false; else if (left == NULL && right == NULL) return true; else if (left->val != right->val) return false; else return compare(left->left, right->right) && compare(left->right, right->left); } bool isSymmetric(TreeNode* root) { if (root == NULL) return true; return compare(root->left, root->right); }
- 对称二叉树要比较的不是左右节点,而是根节点的左子树和右子树是否能反转
-
有点东西的写法
ListNode *getIntersectionNode(ListNode *headA, ListNode *headB) { ListNode *A = headA, *B = headB; // 核心在于交换头节点 while (A != B) { A = A != nullptr ? A->next : headB; B = B != nullptr ? B->next : headA; } return A; }
判断是否为平衡二叉树
- 递归
- 由于尾递归优化,效率很高
// 计算最大深度 int depth(TreeNode* cur) { //计算最大深度 return (cur == nullptr) ? 0 : max(depth(cur->left), depth(cur->right)) + 1; } bool isBalanced(TreeNode* root) { return (root == nullptr) ? true : abs(depth(root->left) - depth(root->right)) <= 1 && isBalanced(root->left) && isBalanced(root->right); }
- 迭代法复杂且优化困难
二叉树的所有路径
- 递归
- 数字转化成字符串
to_string(number)
- 字符串后追加子串
str.append(subStr)
- 字符串删除某个位置之后的字符
str.erase(position)
// 数字型 void dfs(TreeNode*root,vector<int>path, vector<vector<int>> &res) { if(!root) return; //根节点为空直接返回 // 中 path.push_back(root->val); //作出选择 if(!root->left && !root->right) //如果到叶节点 { res.push_back(path); return; } // 左 dfs(root->left,path,res); //继续递归 // 右 dfs(root->right,path,res); } // 字符型 void binaryTree(TreeNode* root,string path,vector<string>&res) { if(root==NULL) return ; path.append(to_string(root->val)); path.append("->"); if(root->left==NULL&&root->right==NULL{ path.erase(path.length()-2); res.push_back(path); } binaryTree(root->left,path,res); binaryTree(root->right,path,res); } vector<string> binaryTreePaths(TreeNode* root) { string path; vector<string>res; binaryTree(root,path,res); return res; }
- 数字转化成字符串
左叶子之和
- 求二叉树的左叶子之和
- 遍历所有节点,对所求的特殊节点进行约束求值
void postorder(TreeNode *root, int &result){ if(root == nullptr) return ; if(root->left) postorder(root->left, result); if(root->right) postorder(root->right, result); // 中 if(root->left != nullptr && root->left->left == nullptr &&root->left->right == nullptr ) result += root->left->val; }
- 有点东西的写法
int sumOfLeftLeaves(TreeNode* root) { // 初始化 stack<TreeNode*> st; if(root != nullptr) st.push(root); int res = 0; // 迭代 while(!st.empty()){ TreeNode* cur = st.top(); if(cur != nullptr){ st.pop(); st.push(cur); st.push(nullptr); if(cur->right) st.push(cur->right); if(cur->left) st.push(cur->left); }else{ st.pop(); cur = st.top(); st.pop(); if(cur->left != nullptr && cur->left->left == nullptr && cur->left->right == nullptr ) res += cur->left->val; } } // 结果处理 return res; }
求二叉树最左下的叶子
- 513. 找树左下角的值
- 层次遍历最后一层的第一个,就是最左下的叶子
int findBottomLeftValue(TreeNode* root) { queue<TreeNode *> q; if(root != nullptr) q.push(root); int res = 0; while(!q.empty()){ int size = q.size(); for(int i= 0; i < size; ++i){ TreeNode * cur = q.front(); q.pop(); // 每层的第一个,即最左的节点 if(i == 0) res = cur->val; if(cur->left) q.push(cur->left); if(cur->right) q.push(cur->right); } } return res; }
符合总和的路径
- 112. 路径总和
- 层次遍历最后一层的第一个,就是最左下的叶子
bool hasPathSum(TreeNode* root, int targetSum) { // 初始化 stack<TreeNode*> st; if(root != nullptr) st.push(root); int sum = 0; // 迭代 while(!st.empty()){ TreeNode *cur = st.top(); if(cur != nullptr){ st.pop(); st.push(cur); st.push(nullptr); sum += cur->val; if(cur->right) st.push(cur->right); if(cur->left) st.push(cur->left); }else{ st.pop(); cur = st.top(); st.pop(); // 节点判断 if(sum == targetSum&& cur->left == nullptr && cur->right == nullptr){ return true; }else{// 回溯 sum -= cur->val; } } } return false; }
106. 从中序与后序遍历序列构造二叉树
- 106. 从中序与后序遍历序列构造二叉树
- 通过始末位置指示容器范围,避免每次调用的vector创建开销
// 中序区间:[inorderBegin, inorderEnd),后序区间[postorderBegin, postorderEnd) TreeNode* traversal ( vector<int>& inorder, int inorderBegin, int inorderEnd, vector<int>& postorder, int postorderBegin, int postorderEnd ){ // 每次都是先从后序找,所以后序没有即完成 if (postorderBegin == postorderEnd) return NULL; // 分界点为后序最后一个 int rootValue = postorder[postorderEnd - 1]; TreeNode* root = new TreeNode(rootValue); if (postorderEnd - postorderBegin == 1) return root; // 查找前序序列中的分界下标 int delimiterIndex; for (delimiterIndex = inorderBegin; delimiterIndex < inorderEnd; delimiterIndex++) { if (inorder[delimiterIndex] == rootValue) break; } // 切割中序数组 // 左中序区间,左闭右开[leftInorderBegin, leftInorderEnd) int leftInorderBegin = inorderBegin; int leftInorderEnd = delimiterIndex; // 右中序区间,左闭右开[rightInorderBegin, rightInorderEnd) int rightInorderBegin = delimiterIndex + 1; int rightInorderEnd = inorderEnd; // 切割后序数组 // 左后序区间,左闭右开[leftPostorderBegin, leftPostorderEnd) int leftPostorderBegin = postorderBegin; int leftPostorderEnd = postorderBegin + delimiterIndex - inorderBegin; // 终止位置是 需要加上 中序区间的大小size // 右后序区间,左闭右开[rightPostorderBegin, rightPostorderEnd) int rightPostorderBegin = postorderBegin + (delimiterIndex - inorderBegin); int rightPostorderEnd = postorderEnd - 1; // 排除最后一个元素,已经作为节点了 // root->left = traversal(inorder, leftInorderBegin, leftInorderEnd, postorder, leftPostorderBegin, leftPostorderEnd); root->right = traversal(inorder, rightInorderBegin, rightInorderEnd, postorder, rightPostorderBegin, rightPostorderEnd); return root; } TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) { if (inorder.size() == 0 || postorder.size() == 0) return NULL; // 左闭右开的原则 return traversal(inorder, 0, inorder.size(), postorder, 0, postorder.size()); }
654. 最大二叉树
- 654. 最大二叉树
- 通过始末位置指示容器范围,避免每次调用的vector创建开销
// 在左闭右开区间[left, right),构造二叉树 TreeNode* traversal(vector<int>& nums, int left, int right) { // 构建完成 if (left >= right) return nullptr; // 分割点下标:maxValueIndex int maxValueIndex = left; for (int i = left + 1; i < right; ++i) { if (nums[i] > nums[maxValueIndex]) maxValueIndex = i; } // 创建节点 TreeNode* root = new TreeNode(nums[maxValueIndex]); // 左闭右开:[left, maxValueIndex) root->left = traversal(nums, left, maxValueIndex); // 左闭右开:[maxValueIndex + 1, right) root->right = traversal(nums, maxValueIndex + 1, right); return root; }
654. 最大二叉树
- 617. 合并二叉树
- 如果两颗树有个相同位置的节点一个为空,另一个不是。则应该直接链接过去,因为这样可以保证后面的也过去
// 递归 TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) { if (t1 == NULL) return t2;// 其中一个为空则返回另一个 if (t2 == NULL) return t1; // 重新定义新的节点,不修改原有两个树的结构 TreeNode* root = new TreeNode(0); root->val = t1->val + t2->val; root->left = mergeTrees(t1->left, t2->left);// 直接链接 root->right = mergeTrees(t1->right, t2->right); return root; } // 迭代 TreeNode* mergeTrees(TreeNode* t1, TreeNode* t2) { if (t1 == NULL) return t2; if (t2 == NULL) return t1; queue<TreeNode*> que; que.push(t1); que.push(t2); while(!que.empty()) { TreeNode* node1 = que.front(); que.pop(); TreeNode* node2 = que.front(); que.pop(); // 此时两个节点一定不为空,val相加 node1->val += node2->val; // 如果两棵树左节点都不为空,加入队列 if (node1->left != NULL && node2->left != NULL) { que.push(node1->left); que.push(node2->left); } // 如果两棵树右节点都不为空,加入队列 if (node1->right != NULL && node2->right != NULL) { que.push(node1->right); que.push(node2->right); } // 当t1的左节点 为空 t2左节点不为空,就赋值过去 if (node1->left == NULL && node2->left != NULL) { node1->left = node2->left; } // 当t1的右节点 为空 t2右节点不为空,就赋值过去 if (node1->right == NULL && node2->right != NULL) { node1->right = node2->right; } } return t1; }
🚩点此跳转到首行↩︎
参考博客
- 代码随想录
- letcode