数据结构--二叉树随记

二叉树主要分为四类:满二叉树、完全二叉树、二叉搜索树、平衡二叉搜索树。

高度,深度,层

image-20191216111731279

满二叉树

满二叉树就是每一层节点都是满的,整棵树像一个正三角形:

满二叉树有个优势,就是它的节点个数很好算。假设深度为 h,那么总节点数就是 2^h - 1,等比数列求和。

完全二叉树

完全二叉树是指,除了最后一层,二叉树其他每层都必须是满的,且最下面一层的节点都集中在该层最左边的若干位置:

如下:

平衡二叉树

平衡二叉树定义为:一个二叉树每个节点 的左右两个子树的高度差的绝对值不超过1。如下图:

二叉搜索树

前面介绍的树,都没有数值的,而二叉搜索树是有数值的了,二叉搜索树是一个有序树

  • 若它的左子树不空,则左子树上所有结点的值均小于它的根结点的值;
  • 若它的右子树不空,则右子树上所有结点的值均大于它的根结点的值;
  • 它的左、右子树也分别为二叉排序树

可以记成【左小右大】:

平衡二叉搜索树

它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树:

C++中map、set、multimap,multiset的底层实现都是平衡二叉搜索树,所以map、set的增删操作时间时间复杂度是log(n)。

二叉树的存储方式

链式存储

基于指针或者引用的二叉链式存储法:

顺序存储

基于数组的顺序存储法:

如果父节点的数组下标是 i,那么它的左孩子就是 i * 2 + 1,右孩子就是 i * 2 + 2。

二叉树遍历方式

二叉树主要有两种遍历方式:

  1. 深度优先遍历:先往深走,遇到叶子节点再往回走。
  2. 广度优先遍历:一层一层的去遍历。

其中:

  • 深度优先遍历
    • 前序遍历(递归法,迭代法)
    • 中序遍历(递归法,迭代法)
    • 后序遍历(递归法,迭代法)
  • 广度优先遍历
    • 层次遍历(迭代法)

深度优先遍历
  • 前序遍历:中左右
  • 中序遍历:左中右
  • 后序遍历:左右中

这里的左、中、右指的是子树,而不是节点。

二叉树的定义(cpp):

  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){}};//TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right){}为构造函数 
//有构造函数,定义初始值为9的节点:TreeNode* a = new TreeNode(9);// 你也可以这样构建一棵二叉树:
TreeNode* root = new TreeNode(1);
root->left = new TreeNode(2);
root->right = new TreeNode(3);
root->left->left = new TreeNode(4);
root->right->left = new TreeNode(5);
root->right->right = new TreeNode(6);// 构建出来的二叉树是这样的:
//     1
//    / \
//   2   3
//  /   / \
// 4   5   6

二叉树的遍历(深度优先遍历)

递归

三要素:

  1. 确定递归函数的参数和返回值: 确定哪些参数是递归的过程中需要处理的,那么就在递归函数里加上这个参数, 并且还要明确每次递归的返回值是什么进而确定递归函数的返回类型。

  2. 确定终止条件: 写完了递归算法, 运行的时候,经常会遇到栈溢出的错误,就是没写终止条件或者终止条件写的不对,操作系统也是用一个栈的结构来保存每一层递归的信息,如果递归没有终止,操作系统的内存栈必然就会溢出。

  3. 确定单层递归的逻辑: 确定每一层递归需要处理的信息。在这里也就会重复调用自己来实现递归的过程。

前序遍历:

144. 二叉树的前序遍历 - 力扣(LeetCode)

demo

/*** 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 traversal(TreeNode* cur,vector<int>& vec){if(cur == nullptr) return;vec.push_back(cur->val); //中traversal(cur->left,vec); //左traversal(cur->right,vec); //右}vector<int> preorderTraversal(TreeNode* root) {vector<int>vec;traversal(root,vec);return vec;}
};

中序遍历:

94. 二叉树的中序遍历 - 力扣(LeetCode)

demo

/*** 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 traversal(TreeNode* cur,vector<int>& vec){if(cur == nullptr) return;traversal(cur->left,vec); //左vec.push_back(cur->val); //中      traversal(cur->right,vec); //右} vector<int> inorderTraversal(TreeNode* root) {vector<int>vec;traversal(root,vec);return vec;}
};

后序遍历:

145. 二叉树的后序遍历 - 力扣(LeetCode)

demo

/*** 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 tranversal(TreeNode* cur,vector<int>& vec){if(cur == nullptr) return;tranversal(cur->left,vec); //左tranversal(cur->right,vec); //右vec.push_back(cur->val); //中}vector<int> postorderTraversal(TreeNode* root) {vector<int>vec;tranversal(root,vec);return vec;}
};

N叉树前序遍历:

589. N叉树的前序遍历

/*
// Definition for a Node.
class Node {
public:int val;vector<Node*> children;Node() {}Node(int _val) {val = _val;}Node(int _val, vector<Node*> _children) {val = _val;children = _children;}
};
*/class Solution {
public:void transeve(Node* cur,vector<int>& vec){if(cur == NULL) return;vec.push_back(cur->val);int childrenSize = cur->children.size();for(int i=0;i<childrenSize;i++){transeve(cur->children[i],vec);}}vector<int> preorder(Node* root) {vector<int>result;transeve(root,result);return result;}
};

迭代

前序遍历:

其实可以这么理解:前序遍历是 “中 左 右”,那么此时我应该先弹出“中”---也就是根节点本身,此时根节点必须在顶层,所以左右子树先不放进去,我们把左右子树连带着其儿子一起分别看成是两个整体,由于栈的特性是先进后出,我们只能需要顶层的元素,所以按照“中 左 右”的顺序,右应该在左的前面进入,所以我们的顺序应该是:“中”进、“中”出、“右进”、“左进”、“左出”、“右出”。这里我们把左右子树包括其儿子一起看成了左右两个整体,当“右进”等一系列操作的时候,我们在其整体中循环以上操作即可。

由此可以写出前序遍历的迭代法代码:

/*** 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:vector<int> preorderTraversal(TreeNode* root) {vector<int>result;stack<TreeNode*>st;if(root == nullptr) return result;st.push(root);while(!st.empty()){TreeNode* cur = st.top();st.pop();result.push_back(cur->val); //中if(cur->right) st.push(cur->right); //右if(cur->left) st.push(cur->left); //左}return result;}
};

二叉树前序遍历 - 力扣(LeetCode)

中序遍历:

其实就是一直遍历左边(“左”)然后到NULL的时候再取出栈中的根节点(“中”),然后跟节点的右边(“右”)。

中序遍历的迭代法代码:

/*** 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:vector<int> inorderTraversal(TreeNode* root) {vector<int>result;stack<TreeNode*>st;if(root == nullptr) return result;TreeNode* cur = root;while(!st.empty() || cur != nullptr){if(cur != nullptr){st.push(cur);cur = cur->left;}else{cur = st.top();st.pop();//此时的节点已经到左子树根节点了,左子树A的左儿子是AL,右儿子是AR,没有第四层了,这时候左儿子没有左右子树了,但是仍然会查找一遍是否有左右子树,发现都没有后,取st.top()时,就是左子树A了,这时候才去查找左子树A的右儿子AR。循环重复。result.push_back(cur->val);cur = cur->right;}}return result;}
};

二叉树的中序遍历 - 力扣(LeetCode)

后序遍历:

再来看后序遍历,先序遍历是中左右,后序遍历是左右中,那么我们只需要调整一下先序遍历的代码顺序,就变成中右左的遍历顺序,然后在反转result数组,输出的结果顺序就是左右中了,如下图:

后序遍历的迭代法代码:

/*** 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:vector<int> postorderTraversal(TreeNode* root) {vector<int>result;stack<TreeNode*>st;if(root == nullptr) return result;st.push(root);while(!st.empty()){TreeNode* cur = st.top();st.pop();result.push_back(cur->val); //中if(cur->left) st.push(cur->left); //右if(cur->right) st.push(cur->right); //左}reverse(result.begin(),result.end()); //反转 变成: 左 右 中return result;}
};

二叉树后序遍历 - 力扣(LeetCode)

二叉树的遍历(广度优先遍历)

层序遍历:

层序遍历一个二叉树。就是从左到右一层一层的去遍历二叉树。需要借用一个辅助数据结构即队列来实现,队列先进先出,符合一层一层遍历的逻辑,而用栈先进后出适合模拟深度优先遍历也就是递归的逻辑。

我们看下代码实现:

/*** 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:vector<vector<int>> levelOrder(TreeNode* root) {queue<TreeNode*>q;vector<vector<int>>result;if(root == nullptr) return result;q.push(root);while(!q.empty()){int size = q.size();   //size必须为固定值,不能用q.size(),q.size()是不断变化的vector<int>vec; //用里面的vector记录每一层的数值for(int i=0;i<size;i++){ //一个节点出去后 就把其左右子树放进队列中TreeNode* cur = q.front();q.pop();vec.push_back(cur->val);if(cur->left) q.push(cur->left);if(cur->right) q.push(cur->right);}result.push_back(vec); //外面的vector记录全部层}return result;}
};

102. 二叉树的层序遍历 - 力扣(LeetCode)

N叉树层序遍历:
/*
// Definition for a Node.
class Node {
public:int val;vector<Node*> children;Node() {}Node(int _val) {val = _val;}Node(int _val, vector<Node*> _children) {val = _val;children = _children;}
};
*///注意Node的children是vector属性,所以我们可以遍历其vector,也是板子题,然后就是,cur的每个children都需要放进队列中,因为这样我们才能去children中寻找是否还有子节点!!
class Solution {
public:vector<vector<int>> levelOrder(Node* root) {queue<Node*>q;vector<vector<int>>result;if(root == NULL) return result;q.push(root);while(!q.empty()){vector<int>vec;int size = q.size();for(int i=0;i<size;i++){Node* cur = q.front();q.pop();vec.push_back(cur->val);for(int j = 0;j<cur->children.size();j++){if(cur->children[j])q.push(cur->children[j]);}}result.push_back(vec);}return result;}
};

N叉树层序遍历 - 力扣(LeetCode)

需要注意的是,我们需要把每个节点的子节点都放进queue中,这样我们才可以在queue中查找改子节点是否还有左右子树!!!此外,时刻注意size是必须的,因为此时的size是纪律这一层的节点数。

填充每个节点的下一个右侧节点指针:

填充它的每个 next 指针,让这个指针指向其下一个右侧节点。如果找不到下一个右侧节点,则将 next 指针设置为 NULL。如下图:

还是在层序遍历的基础上 分类讨论:节点是最后一个节点 or 不是最后一个节点

代码:

/*
// Definition for a Node.
class Node {
public:int val;Node* left;Node* right;Node* next;Node() : val(0), left(NULL), right(NULL), next(NULL) {}Node(int _val) : val(_val), left(NULL), right(NULL), next(NULL) {}Node(int _val, Node* _left, Node* _right, Node* _next): val(_val), left(_left), right(_right), next(_next) {}
};
*/
// 还是在层序遍历的基础上 分类讨论:节点是最后一个节点 or 不是最后一个节点
// 注意判断queue是否为空!
class Solution {
public:Node* connect(Node* root) {queue<Node*> q;if (root == NULL)return NULL;q.push(root);while (!q.empty()) {int size = q.size();for (int i = 0; i < size; i++) {Node* cur = q.front();q.pop();if (i == size - 1) {cur->next = NULL;} else if (i != size - 1 && !q.empty()) {//在同一层考虑即可 因为前面已经把当前节点弹出了:q.pop(),所以可以直接获取同一层的右边节点:q.front()cur->next = q.front();}if (cur->left)q.push(cur->left);if (cur->right)q.push(cur->right);}}return root;}
};

填充每个节点的下一个右侧节点指针 - 力扣(LeetCode)

二叉树的最大深度:

给定一个二叉树 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:int maxDepth(TreeNode* root) {queue<TreeNode*> q;if (root == nullptr)return 0;q.push(root);int ans = 0;while (!q.empty()) {int size = q.size();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);}ans++;}return ans;}
};

思路很简单,只需要在每层结束后ans++即可。

二叉树的最小深度

给定一个二叉树,找出其最小深度。最小深度是从根节点到最近叶子节点的最短路径上的节点数量。

说明:叶子节点是指没有子节点的节点。

​​​​​​​

代码:

/*** 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) {}* };*/
// 层次遍历 判断当前节点是否没有左右子树 如果没有的话 当前节点就是最短路径的节点
// 每层都用cell++
class Solution {
public:int minDepth(TreeNode* root) {queue<TreeNode*> q;if (root == nullptr)return 0;q.push(root);int cell = 0;int ans = 0;bool isok = false;while (!q.empty()) {int size = q.size();cell++;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);if (cur->left == nullptr && cur->right == nullptr) {ans = cell;isok = true;break;}}if (isok)break;}return ans;}
};

递归法同样可以解决:

/*** 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:int dfs(TreeNode* cur) {if (cur == nullptr) {return 0;}if (cur->left == nullptr && cur->right != nullptr) {return dfs(cur->right) + 1;}if (cur->left != nullptr && cur->right == nullptr) {return (dfs(cur->left) + 1);}return min(dfs(cur->right), dfs(cur->left)) + 1;}int minDepth(TreeNode* root) { return dfs(root); }
};

总结:二叉树的层序遍历需要借助队列实现。

题目:

  • 102.二叉树的层序遍历(opens new window)
  • 107.二叉树的层次遍历II(opens new window)
  • 199.二叉树的右视图(opens new window)
  • 637.二叉树的层平均值(opens new window)
  • 429.N叉树的层序遍历(opens new window)
  • 515.在每个树行中找最大值(opens new window)
  • 116.填充每个节点的下一个右侧节点指针(opens new window)
  • 117.填充每个节点的下一个右侧节点指针II(opens new window)
  • 104.二叉树的最大深度(opens new window)
  • 111.二叉树的最小深度

对称二叉树

看是否轴对称,迭代+分类讨论:

/*** 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:bool isSymmetric(TreeNode* root) {queue<TreeNode*> q;if (root == nullptr)return true;q.push(root->left);q.push(root->right);while (!q.empty()) {TreeNode* nodeLeft = q.front();q.pop();TreeNode* nodeRight = q.front();q.pop();if(nodeLeft == nullptr && nodeRight == nullptr) continue;//分类讨论if (nodeLeft != nullptr &&nodeRight != nullptr && nodeLeft->val != nodeRight->val) {return false;}else if(nodeLeft == nullptr && nodeRight != nullptr){return false;}else if(nodeLeft != nullptr && nodeRight == nullptr){return false;}//注意顺序 要对称!!!!q.push(nodeLeft->left);q.push(nodeRight->right);q.push(nodeLeft->right);q.push(nodeRight->left);}return true;}
};

注意入队顺序!!!

对称二叉树 - 力扣(LeetCode)

完全二叉树的节点个数

给出一个完全二叉树,求出该树的节点个数。

示例 1:

  • 输入:root = [1,2,3,4,5,6]
  • 输出:6
/*** 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:
//     int countNodes(TreeNode* root) {
//         queue<TreeNode*>q;
//         if(root == nullptr) return 0;
//         q.push(root);
//         int ans = 0;
//         while(!q.empty()){
//             int size = q.size();
//             for(int i = 0;i < size;i++){
//                 TreeNode* cur = q.front();
//                 q.pop();
//                 ans++;
//                 if(cur->left) q.push(cur->left);
//                 if(cur->right) q.push(cur->right);
//             }//         }
//         return ans;//     }
// };
//递归法
class Solution {
public:int transeve(TreeNode* cur) {if (cur == nullptr)return 0;return transeve(cur->left) + transeve(cur->right) + 1;}int countNodes(TreeNode* root) {int ans = transeve(root);return ans;}
};

完全二叉树的节点个数 - 力扣(LeetCode)

主要是递归不熟。。。。

是否是平衡二叉树

给定一个二叉树,判断它是否是 “平衡二叉树” 、即任意节点的左右子树高度差是否总是小于等于1。

这里涉及到高度与深度的选择:

image-20191216111731279

  我们来分析一下:这道题求的是左右子树的高度差的绝对值不超过1,很明显求的是“高度”,或者我们换个思路,这道题我们可以从子节点反向递归回父节点,从子树往上递归+1,父节点取左右子树的高度最大值+1,然后如果左右子树的差值大于1的话,直接返回-1即可,这里需要用到后序遍历(左右中)。

一般来说:

  • 求高度用后序遍历
  • 求深度用前序遍历

递归code:

/*** 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) {}* };*///可以看出是求高度的 -> 我们可以反向思维 从子树往上递归+1 父节点取左右子树的高度最大值+1 然后如果左右子树的差值大于1的话 直接返回-1即可 这里需要用到后序遍历//后序遍历 左右中 //求高度一般是后序遍历 求深度是前序遍历
class Solution {
public:int transerve(TreeNode* cur){if(cur == nullptr) return 0;int leftHeight = transerve(cur->left); //左int rightHeight = transerve(cur->right); //右if(leftHeight == -1)return -1;if(rightHeight == -1)return -1;//中if(abs(leftHeight - rightHeight) > 1){return -1;}else{return max(leftHeight,rightHeight) + 1;}}bool isBalanced(TreeNode* root) {if(root == nullptr) return true;if(transerve(root) == -1){return false;}return true;}
};

这里顺便巩固一下递归。

首先用递归我们要明确一下递归三部曲:

  1. 明确递归函数的参数和返回值:参数:当前传入节点。 返回值:以当前传入节点为根节点的树的高度。
  2. 明确终止条件:递归的过程中依然是遇到空节点了为终止,返回0,表示当前节点为根节点的树高度为0。也可以这么认为:当遇到没有左右子树的节点的时候,也就是说这个cur->left、cur->right都为NULL的话,此时cur->left、cur->right都返回0,表明这个空节点的高度为0,而cur高度为1。
  3. 明确单层递归的逻辑:如何判断以当前传入节点为根节点的二叉树是否是平衡二叉树呢?当然是其左子树高度和其右子树高度的差值。

代码递归的意思就是说,先遍历左右子树,然后根据左右子树返回的高度值来进行逻辑判断。

二叉树所有路径

递归+回溯

这道题根据输出结果可知用dfs前序遍历是最方便的,本体难点涉及到需要用path记录路径并且回溯。既然是dfs,用递归是最方便的(相比于栈的迭代法)。

首先给出本题code:

/*** 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 transeve(TreeNode* cur, vector<int>& path, vector<string>& result) {if (cur == nullptr)return;// 中 : 记录路径path.push_back(cur->val);// 终止条件 -> 当为叶节点时 返回路径结果if (cur->left == nullptr && cur->right == nullptr) {// int 转 stringstring pathString = "";for (int i = 0; i < path.size(); i++) {// int 转为 stringpathString += to_string(path[i]);if (i != path.size() - 1) {pathString += "->";}}// 将转换结果放进vector中result.push_back(pathString);}// 左if (cur->left) {transeve(cur->left, path, result);// 回溯path.pop_back();}// 右if (cur->right) {transeve(cur->right, path, result);// 回溯path.pop_back();}}vector<string> binaryTreePaths(TreeNode* root) {vector<string> result;vector<int> path;if (root == nullptr)return result;transeve(root, path, result);return result;}
};

既然是递归的话,就还是按照递归三部曲:

  1. 明确参数和返回值:这里需要多一个path来记录路径,可以先用vector<int>来记录,对于结果的时候转换成string即可。
  2. 明确终止条件:求的是根节点到叶子节点的路径,而且还是前序遍历,那么肯定是以叶子节点为终止条件,即cur->left == nullptr && cur->right == nullptr。
  3. 明确单层递归逻辑:无非就是记录path,把此层值放进path中,并且需要回溯一下。

关于回溯:

首先记得,回溯一定要和递归放一起!!!!你左右递归的时候就要立刻回溯!!!

迭代法

个人觉得迭代法其实更好理解一些,code:

/*** 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) {}* };*///dfs前序遍历 栈迭代法实现//用两个栈 分别存“节点”和“走过的路径” //两点注意: /*一、栈的先进后出的 所以前序遍历(中左右)要改成 (中右左)二、路径沿用父节点的路径来进行相加
*/
class Solution {
public:vector<string> binaryTreePaths(TreeNode* root) {stack<TreeNode*>st;stack<string>pathSt;vector<string>result;if(root == nullptr) return result;st.push(root);pathSt.push(to_string(root->val));while(!st.empty()){//取出节点的同时 也要取出节点路径TreeNode* cur = st.top();st.pop();string path = pathSt.top();pathSt.pop();if(cur->left == nullptr && cur->right == nullptr){result.push_back(path);}//右if(cur->right){st.push(cur->right);//沿用父节点的路径pathSt.push(path + "->" + to_string(cur->right->val));}//左if(cur->left){st.push(cur->left);//沿用父节点的路径pathSt.push(path + "->" + to_string(cur->left->val));}}return result;}
};

二叉树的所有路径 - 力扣(LeetCode)

路径总和

给你二叉树的根节点 root 和一个表示目标和的整数 targetSum 。判断该树中是否存在 根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和 targetSum 。如果存在,返回 true ;否则,返回 false

其实和二叉树所有路径的迭代法一样的,都是子节点继承父节点的某些东西。

code:

/*** 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) {}* };*///前序遍历 dfs栈迭代法 中 右 左//终止条件 此节点没有左右子树(到达最底层了)//两个栈 其中一个用来维护此节点的总值
class Solution {
public:bool hasPathSum(TreeNode* root, int targetSum) {stack<TreeNode*>st;stack<int>ve; //维护此节点的总值bool result = false;if(root == nullptr) return false;st.push(root);ve.push(root->val);while(!st.empty()){TreeNode* cur = st.top();st.pop();int nowValue = ve.top();ve.pop();//中if(!cur->left && !cur->right){if(nowValue == targetSum){result = true;break;}}//右if(cur->right){st.push(cur->right);ve.push(nowValue + cur->right->val); //继承父节点的值}//左if(cur->left){st.push(cur->left);ve.push(nowValue + cur->left->val);//继承父节点的值}}return result;}
};

路径总和 - 力扣(LeetCode)

左叶子之和

给定二叉树的根节点 root ,返回所有左叶子之和。

dfs栈迭代法 前序遍历:

用两个栈 分别记录节点和节点是左子树还是右子树

终止条件->当此时的节点没有左右子树(即已经在最低层了),看此时的节点是左子树还是右子树

其实迭代法是一一对应的,每个点都会遍历一遍。

code:

/*** 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) {}* };*///前序遍历 dfs栈迭代法 中 右 左//用一个bool值判断此时是左子树还是右子树 终止条件是此时的节点没有左右子树(即已经在最低层了)
class Solution {
public:int sumOfLeftLeaves(TreeNode* root) {stack<TreeNode*>st;//bool判断此时是左子树还是右子树stack<bool>lrSt;int result = 0;if(root == nullptr) return result;st.push(root);lrSt.push(false);//根节点不算叶子 所以是falsewhile(!st.empty()){TreeNode* cur = st.top();st.pop();bool lr = lrSt.top();lrSt.pop();//中if(!cur->left && !cur->right && lr == true){result += cur->val;}//右if(cur->right){st.push(cur->right);lrSt.push(false);}//左if(cur->left){st.push(cur->left);lrSt.push(true);}}return result;}
};

左叶子之和 - 力扣(LeetCode)

从中序与后序遍历序列构造二叉树

给定两个整数数组 inorder 和 postorder ,其中 inorder 是二叉树的中序遍历, postorder 是同一棵树的后序遍历,请你构造并返回这颗 二叉树 。

重点!!!

递归 + 切割

code:

/*** 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:TreeNode* transeve(vector<int>& inorder,vector<int>& postorder){if (postorder.size() == 0) return NULL; //在函数里面要特判一次 返回空指针(最底部的叶子节点)!!!TreeNode* root = new TreeNode(postorder[postorder.size()-1]);if(postorder.size() == 1) return root;postorder.pop_back();TreeNode* cur = root;vector<int>leftInorder;vector<int>rightInorder;int i=0;for(;i<inorder.size();i++){if(inorder[i] == root->val){break;}else{leftInorder.push_back(inorder[i]);}}i++;for(;i<inorder.size();i++){rightInorder.push_back(inorder[i]);}vector<int>leftPostorder;vector<int>rightPostorder;for(i=0;i<leftInorder.size();i++){leftPostorder.push_back(postorder[i]);}for(;i<rightInorder.size() + leftInorder.size();i++){rightPostorder.push_back(postorder[i]);}//递归cur->left = transeve(leftInorder,leftPostorder);cur->right = transeve(rightInorder,rightPostorder);return root;}TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {return transeve(inorder,postorder);}
};

从中序与后序遍历序列构造二叉树 - 力扣(LeetCode)

优化版code:

/*** 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:TreeNode* transeve(vector<int>& inorder, vector<int>& postorder) {if (postorder.size() == 0)return nullptr;TreeNode* root = new TreeNode(postorder[postorder.size() - 1]);if (postorder.size() == 1)return root;TreeNode* cur = root;postorder.pop_back();int i = 0;for (i = 0; i < inorder.size(); i++) {if (inorder[i] == root->val) {break;}}vector<int> leftInorder(inorder.begin(), inorder.begin() + i);vector<int> rightInorder(inorder.begin() + i + 1, inorder.end());vector<int> leftPostoreder(postorder.begin(),postorder.begin() + leftInorder.size());vector<int> rightPostoreder(postorder.begin() + leftInorder.size(),postorder.end());cur->left = transeve(leftInorder, leftPostoreder);cur->right = transeve(rightInorder, rightPostoreder);return root;}TreeNode* buildTree(vector<int>& inorder, vector<int>& postorder) {return transeve(inorder, postorder);}
};

合并二叉树

两个队列同时遍历两个数 注意分类讨论

/*** 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) {}* };*///用两个队列同时遍历两个树。以root1为主要的 最后返回的也是root1 class Solution {
public:TreeNode* mergeTrees(TreeNode* root1, TreeNode* root2) {queue<TreeNode*> q1;queue<TreeNode*> q2;if (root1 == nullptr && root2 == nullptr)return nullptr;if (root1 != nullptr && root2 == nullptr)return root1;if (root1 == nullptr && root2 != nullptr)return root2;q1.push(root1);q2.push(root2);while (!q1.empty() || !q2.empty()) {TreeNode* cur1 = q1.front();q1.pop();TreeNode* cur2 = q2.front();q2.pop();//两个树节点都存在 直接相加if (cur1 != nullptr && cur2 != nullptr) {cur1->val += cur2->val;}//两个树的左节点都存在 左节点都放入队列中if (cur1->left != nullptr && cur2->left != nullptr) {q1.push(cur1->left);q2.push(cur2->left);}if (cur1->right != nullptr && cur2->right != nullptr) {q1.push(cur1->right);q2.push(cur2->right);}//root1的左节点没有的话 就把root2的左节点给root1if (cur1->left == nullptr && cur2->left != nullptr) {cur1->left = cur2->left;}//root1的右节点没有的话 就把root2的右节点给root1if (cur1->right == nullptr && cur2->right != nullptr) {cur1->right = cur2->right;}}return root1;}
};

合并二叉树 - 力扣(LeetCode)

验证二叉搜索树

给定一个二叉树,判断其是否是一个有效的二叉搜索树。

假设一个二叉搜索树具有如下特征:

  • 节点的左子树只包含小于当前节点的数。
  • 节点的右子树只包含大于当前节点的数。
  • 所有左子树和右子树自身必须也是二叉搜索树。

code:

/*** 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:bool isValidBST(TreeNode* root) {stack<TreeNode*> st;TreeNode* cur = root;TreeNode* pre = NULL; // 记录前一个节点while (cur != nullptr || !st.empty()) {if (cur != NULL) {st.push(cur);cur = cur->left; // 左} else {cur = st.top(); // 中st.pop();if (pre != NULL && cur->val <= pre->val)return false;pre = cur; // 保存前一个访问的结点cur = cur->right; // 右}}return true;}
};

98. 验证二叉搜索树 - 力扣(LeetCode)

关于二叉树的做题思路

首先先看是深度搜索还是广度搜索:

  • 深度搜索dfs用递归 or 栈迭代。
  • 广度搜索bfs用队列。

然后看是前中后序遍历 :

  • Tips:栈的特性是先进后出 所以有点特殊: 前序遍历(中右左)、后序遍历(左右中)->栈的前序遍历的代码反转就是了。
  • 递归三部曲牢记。

还有特殊的,双队列、双栈,建议把题目都再做一遍。。。。

参考:

代码随想录 (programmercarl.com)

力扣 (LeetCode) 全球极客挚爱的技术成长平台

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/56846.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

讲一讲Redis五大数据类型的底层实现

讲一讲Redis五大数据类型的底层实现 Redis五大数据类型的底层实现 Redis的五大数据类型分别是字符串&#xff08;String&#xff09;、列表&#xff08;List&#xff09;、哈希&#xff08;Hash&#xff09;、集合&#xff08;Set&#xff09;和有序集合&#xff08;Zset&…

Fake Location 限制解除(运动世界校园,keep......)

一觉起来成绩还是正常的&#xff0c;运动世界校园的审核是非常严格的&#xff0c;因为在这之前&#xff0c;我帮助同学登入别的账号进行跑步&#xff0c;发现过来几天全被检测到了异常,成绩也是直接无效了哈&#xff0c;我们今天再搞一个关于keep的&#xff0c;因为当时关于kee…

pikachu靶场SSRF-curl测试报告

目录 一、测试环境 1、系统环境 2、使用工具/软件 二、测试目的 三、操作过程 1、实现ssrf攻击 四、源代码分析 五、结论 一、测试环境 1、系统环境 渗透机&#xff1a;本机(127.0.0.1) 靶 机&#xff1a;本机(127.0.0.1) 2、使用工具/软件 测试网址&#xff1a;…

DNS 与 ICMP

DNS(Domain Name System)快速了解 DNS 是一整套从域名映射到 IP 的系统 DNS 背景 TCP/IP 中使用 IP 地址和端口号来确定网络上的一台主机的一个程序. 但是 IP 地址不方便记忆 于是人们发明了一种叫主机名的东西, 是一个字符串, 并且使用 hosts 文件来描述主机 名和 IP 地…

微信开发者工具:音乐小程序报错

报错信息 GET http://localhost:3000/1.mp3 net::ERR CONNECTION REFUSED (env: Windows,mp,1.06.2303220;lib:3.6.0) 原因&#xff1a;小程序没有直接获取本地文件&#xff0c;为了提高访问速度&#xff0c;而采用放到网络服务器中网络访问的方式获取文件内容 解决办法&#…

JMeter如何设置HTTP代理服务器?

1、 2、添加线程组 3、设置HTTP代理服务器&#xff0c;目标控制器选择“测试计划>线程组” 过滤掉不需要的信息 4、设置电脑手动代理 5、点击启动&#xff0c;在浏览器操作就可以了

OpenCV-物体跟踪

文章目录 一、物体跟踪的定义二、OpenCV中的物体跟踪算法三、OpenCV物体跟踪的实现步骤四、代码实现五、注意事项 OpenCV是一个开源的计算机视觉和机器学习软件库&#xff0c;它提供了丰富的功能来实现物体跟踪。以下是对OpenCV中物体跟踪的详细解释&#xff1a; 一、物体跟踪的…

Ubuntu16.04安装openssl库

Ubuntu16.04安装openssl库 Chapter1 Ubuntu16.04安装openssl库 Chapter1 Ubuntu16.04安装openssl库 原文链接&#xff1a;https://blog.csdn.net/weixin_36584476/article/details/107321893 记录一下省得忘了 1.首先去openssl官网下载源码www.openssl.org/source/&#xff0…

进程同步、互斥

进程同步、互斥的基本概念 系统中的某些资源&#xff0c;虽然可以提供给多个进程使用&#xff0c;但一个时间段内只允许一个进程访问该资源。 我们把一个时间段内只允许一个进程使用的资源称为临界资源。许多物理设备&#xff08;比如摄像头、打印机&#xff09;都属于临界资源…

python爬虫加解密分析及实现

第一种&#xff1a; 1、找到加密的接口地址&#xff0c;通过加密的接口地址全局搜索 2、通过打断点的方式&#xff0c;操作页面&#xff0c;跑到断点处时&#xff0c;即可找到加密串&#xff0c;如图二&#xff1b; 3、找到用的是哪种加密方式&#xff0c;如&#xff1a; cr…

Unity Apple Vision Pro 保姆级开发教程-准备阶段

视频教程&#xff1a; Unity PolySpatial 开发Apple Vision Pro教程, 三十分钟快速了解 Unity Vision Pro 中文课堂教程地址&#xff1a; Unity3D Vision Pro 开发教程【保姆级】 | Unity 中文课堂 开发Apple Vision Pro 使用原生开发和unity 开发有什么区别 如果你的项目需要…

LSTM反向传播及公式推导

先回顾一下正向传播的公式: 化简一下: 反向传播从下到上逐步求偏导: 对zt求偏导(预测值和标签值相减): zt对未知数wt,ht,bt分别求偏导: ht对ot,Ct求偏导: ot对Net0求偏导: Net0对w0,b0求偏导: .... 总体的思路就是那个公式从下到上逐步对未知数求偏导: 下面是总体的流程…

【AIGC】ChatGPT与人类理解力的共鸣:人机交互中的心智理论(ToM)探索

博客主页&#xff1a; [小ᶻZ࿆] 本文专栏: AIGC | ChatGPT 文章目录 &#x1f4af;前言&#x1f4af;心智理论(Theory of Mind,ToM)心智理论在心理学与神经科学中的重要性心智理论对理解同理心、道德判断和社交技能的重要性结论 &#x1f4af;乌得勒支大学研究对ChatGPT-4…

【C++篇】类与对象深度解析(六):全面剖析拷贝省略、RVO、NRVO优化策略

文章目录 前言 &#x1f4ac; 欢迎讨论&#xff1a;如果你在学习过程中有任何问题或想法&#xff0c;欢迎在评论区留言&#xff0c;我们一起交流学习。你的支持是我继续创作的动力&#xff01; &#x1f44d; 点赞、收藏与分享&#xff1a;觉得这篇文章对你有帮助吗&#xff1…

软件设计模式------工厂方法模式

工厂方法模式&#xff08;Factory Method Pattern&#xff09;&#xff0c;又称工厂模式&#xff0c;也叫虚拟构造器模式&#xff08;Virtual Constructor Pattern&#xff09;或多态工厂模式&#xff08;Polymorphic Pactory Pattern&#xff09;,属于类创建型模式。 我们知道…

WIFI实现透传+接线图

单片机通过TX接WIFI模块的RX将设置的AT代码写入WIFI模块&#xff08;连接WIFI调为设备模式&#xff08;有设备&#xff0c;路由&#xff0c;双模等模式&#xff09;&#xff09; WIFI模块将响应信号通过TX通过CH340发给PC的RX 通过STC-ISP或安信可串口调试助手查看响应信息 …

Golang | Leetcode Golang题解之第495题提莫攻击

题目&#xff1a; 题解&#xff1a; func findPoisonedDuration(timeSeries []int, duration int) (ans int) {expired : 0for _, t : range timeSeries {if t > expired {ans duration} else {ans t duration - expired}expired t duration}return }

qt QGraphicsEffect详解

一、QGraphicsEffect概述 QGraphicsEffect通过挂接到渲染管道并在源&#xff08;例如QGraphicsPixmapItem、QWidget&#xff09;和目标设备&#xff08;例如QGraphicsView的视口&#xff09;之间进行操作来更改元素的外观。它允许开发者为图形项添加各种视觉效果&#xff0c;如…

Java网络编程-简单的API调用

Get请求 - 无参数 安装依赖库 首先需要安装一个库&#xff1a; Okhttp3&#xff0c;这是一个非常流行的 HTTP 库&#xff0c;可以简单、快速的实现 HTTP 调用。 安装 Okhttp3 的方式是在 pom.xml 文件中增加依赖&#xff1a; <!-- https://mvnrepository.com/artifact/co…

【算法】哈希表:49.字母异位词分组

目录 1、题目链接 2、题目介绍 3、解法 初始化设定--图解 步骤图解 4、代码 1、题目链接 49. 字母异位词分组 - 力扣&#xff08;LeetCode&#xff09; 2、题目介绍 3、解法 字母异位词的本质是字符相同但排列不同。因此&#xff0c;我们可以对字符串进行排序&#xf…