🌈hello,你好鸭,我是Ethan,西安电子科技大学大三在读,很高兴你能来阅读。
✔️目前博客主要更新Java系列、项目案例、计算机必学四件套等。
🏃人生之义,在于追求,不在成败,勤通大道。加油呀!
🔥个人主页:Ethan Yankang
🔥推荐:史上最强八股文 || 一分钟看完我的上千篇博客
🔥温馨提示:划到文末发现专栏彩蛋 点击这里直接传送
🔥本篇概览:数据结构与算法 || 详细讲解了代码随想录二刷的二叉树篇。🌈⭕🔥
【计算机领域一切迷惑的源头都是基本概念的模糊,算法除外】
🌈序言
算法乃我长久之志也,此关必过。如今二刷代码随想录,必然收获良多,加油不懈怠!
本次只写题目+代码实现(主要的点全都写在代码注释里面),多题合一。
🔥 前一篇章
🌈思维导图
总结:递归就一句话:
确认递归(函数)是一种操作+我们把一个递归的基本单位的所有情况考虑到就行+注意返回值的直观直觉。
题目:
DFS
144.前序遍历二叉树:
递归:
技巧:
1.使用resList引用作为参数解耦,避免声明成员变量
2.最佳实践:使用辅助函数、private、void、参数为引用,主函数只放函数调用。形成标准。
class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List resList = new LinkedList<Integer>();
preOrder(root, resList);
return resList;
}
private void preOrder(TreeNode root, List<Integer> resList) {
if (root == null) {
return;
}
resList.add(root.val);
preOrder(root.left, resList);
preOrder(root.right, resList);
}
}
迭代:
在每次操作一个节点时,将他及他的左右子节点都取出来,重新放置他们的顺序,在当前节点入栈时加入nulll作为标记确保能操作即可。
class Solution {
public List<Integer> inorderTraversal(TreeNode root) {
var resList = new LinkedList<Integer>();
var stack = new Stack<TreeNode>();
if (root != null)
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.peek();
if (node != null) {
TreeNode tempNode = stack.pop();
if (tempNode.right != null)
stack.push(tempNode.right);
if (tempNode.left != null)
stack.push(tempNode.left);
stack.push(tempNode);
stack.push(null);
} else {
stack.pop();
resList.add(stack.pop().val);
}
}
return resList;
}
}
BFS
102. 二叉树的层序遍历
给你二叉树的根节点 root
,返回其节点值的 层序遍历 。 (即逐层地,从左到右访问所有节点)。
标准:注意这里的root==null之类的判断全部放在辅助函数里面来,会更好,主函数只放函数调用就行。
递归:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ResList = new LinkedList<List<Integer>>();
levelorderByRecursion(root,ResList,0);
return ResList;
}
private void levelorderByRecursion(TreeNode root, List<List<Integer>> ResList ,int deep){
if(root==null)return;
deep++;
if (ResList.size() < deep) {
//当层级增加时,list的Item也增加,利用list的索引值进行层级界定
List<Integer> item = new ArrayList<Integer>();
ResList.add(item);
}
ResList.get(deep-1).add(root.val);
levelorderByRecursion(root.left,ResList,deep);
levelorderByRecursion(root.right,ResList,deep);
}
}
迭代:
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
List<List<Integer>> ResList = new LinkedList<List<Integer>>();
levelorderByWhile(root,ResList);
return ResList;
}
private void levelorderByWhile(TreeNode root, List<List<Integer>> ResList){
if(root==null)return;
var queue=new LinkedList<TreeNode>();
queue.add(root);
while(!queue.isEmpty()){
int size=queue.size();
var item=new LinkedList<Integer>();
while(size-->0){
TreeNode tempNode=queue.remove();
item.add(tempNode.val);
if(tempNode.left!=null)queue.add(tempNode.left);
if(tempNode.right!=null)queue.add(tempNode.right);
}
ResList.add(item);
}
}
226. 翻转二叉树
给你一棵二叉树的根节点 root
,翻转这棵二叉树,并返回其根节点。
一般我们都比较怕这种递归是吧,不知道递归是个啥,绕来绕去就头晕了。
其实记住一句话就好了,将函数看成一种操作,递归就是针对当前对象(引用,基本类型)的所有操作,而且为了整体的条件满足,操作往往是嵌套的。
比如这里的根节点实现了反转的操作后,根节点的左右子节点也要实现这个操作,这样就根节点来说,才算完整,所以左右均在嵌套一层。
class Solution {
public TreeNode invertTree(TreeNode root) {
if(root==null)return null;
swap(root);
invertTree(root.left);
invertTree(root.right);
return root;
}
// 辅助函数最佳实践
private void swap(TreeNode root){
if(root==null)return;
TreeNode temp=root.left;
root.left=root.right;
root.right=temp;
}
}
测试方法:
【这里采用中序遍历是因为一目了然】记住这个特性。
public static void main(String[] args) {
// 创建一个简单的二叉树
TreeNode root = new TreeNode(4);
root.left = new TreeNode(2);
root.right = new TreeNode(7);
root.left.left = new TreeNode(1);
root.left.right = new TreeNode(3);
root.right.left = new TreeNode(6);
root.right.right = new TreeNode(9);
InvertTree inverter = new InvertTree();
System.out.println("翻转前");
inorderTraversal(root);
TreeNode invertedRoot = inverter.invertTree(root);
System.out.println();
System.out.println("翻转后");
// 输出翻转后的树的中序遍历(仅作为一种简单验证方式)
inorderTraversal(invertedRoot);
}
// 中序遍历用于验证
private static void inorderTraversal(TreeNode node) {
if (node == null) return;
inorderTraversal(node.left);
System.out.print(node.val + " ");
inorderTraversal(node.right);
}
结果:
101. 对称二叉树
给你一个二叉树的根节点 root
, 检查它是否轴对称。
确认递归(函数)是一种操作+我们把一个递归的基本单位的所有情况考虑到就行
这里的操作就是让根节点的左右节点是对称的,以及左右节点的左右节点对称性
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root==null)return true;
return doIsSymmetric(root.left,root.right);
}
private boolean doIsSymmetric(TreeNode left,TreeNode right){
// 这里的&&具有短路性质
if(left==null&&right==null)return true;
else if((left!=null&&right!=null)
&&(left.val==right.val)
&&doIsSymmetric(left.left,right.right)
&&doIsSymmetric(left.right,right.left))
return true;
else return false;
}
}
111. 二叉树的最小深度
给定一个二叉树,找出其最小深度。
最小深度是从根节点到最近叶子节点的最短路径上的节点数量。
说明:叶子节点是指没有子节点的节点。
踩坑:Integer是不可变对象,意味着传入finMin中不会被改变,应该用int,踩坑了。
class Solution {
public int minDepth(TreeNode root) {
return findMin(root,0);
}
private int findMin(TreeNode root,int deep){
if(root==null)return deep;
var queue=new LinkedList<TreeNode>();
queue.add(root);
while(!queue.isEmpty()){
int size=queue.size();
deep++;
while(size-->0){
TreeNode tempNode = queue.remove();
if(tempNode.left==null&&tempNode.right==null)return deep;
if(tempNode.left!=null)queue.add(tempNode.left);
if(tempNode.right!=null)queue.add(tempNode.right);
}
}
return deep;
}
}
110. 平衡二叉树
给定一个二叉树,判断它是否是 平衡二叉树。平衡二叉树 是指该树所有节点的左右子树的深度相差不超过 1。
明确3点
1.深度与高度的区别==>深根高叶
深度==>当前节点到根节点的节点数 ==>必然用后序遍历
高度==>当前节点到叶子结点的节点数==>必然用前序遍历
2.求的是高度,必然用的是后序遍历(代码逻辑上是这样的,因为要先从叶子结点算起嘛)
3.必定是用递归==>递归之禅:只要满足基本程序数据单元的所有可能操作就行。
技巧:用整数==1?来判断是否为BST
class Solution {
public boolean isBalanced(TreeNode root) {
return CalHeight(root)!=-1;
}
// 辅助函数用来计算当前节点的高度的
private int CalHeight(TreeNode root){
if(root==null)return 0;
// 左右节点已经不是平衡二叉树了,直接返回
if(CalHeight(root.left)==-1)return -1;
if(CalHeight(root.right)==-1)return -1;
// 本节点不是BST了
if(Math.abs(CalHeight(root.left)-CalHeight(root.right))>1)return -1;
// 满足条件就返回当前节点的深度(就是左右节点的最大深度+1),很自然的事
return Math.max(CalHeight(root.left),CalHeight(root.right))+1;
}
}
257. 二叉树的所有路径
给你一个二叉树的根节点 root
,按 任意顺序 ,返回所有从根节点到叶子节点的路径。
叶子节点 是指没有子节点的节点。
回溯法
方式一 回溯法,大家一起共用PATH,所以使用完了,要把自己之前使用的给腾出来(这里就是遇到叶子结点了,就要开始回退了(跳出循环了)),很自然,就像公共浴室
你洗完澡了,肯定要清理自己的物品,但是你自己的浴室,想怎么造就怎么造。
class Solution {
public List<String> binaryTreePaths(TreeNode root) {
List<String> res = new ArrayList<>();// 存最终的结果
if (root == null) {
return res;
}
List<Integer> paths = new ArrayList<>();// 作为结果中的路径
traversal(root, paths, res);
return res;
}
private void traversal(TreeNode root, List<Integer> paths, List<String> res) {
paths.add(root.val);// 前序遍历,中
// 遇到叶子结点
if (root.left == null && root.right == null) {
// 输出
StringBuilder sb = new StringBuilder();// StringBuilder用来拼接字符串,速度更快
for (int i = 0; i < paths.size() - 1; i++) {
sb.append(paths.get(i)).append("->");
}
sb.append(paths.get(paths.size() - 1));// 记录最后一个节点
res.add(sb.toString());// 收集一个路径
return;
}
// 递归和回溯是同时进行,所以要放在同一个花括号里
if (root.left != null) { // 左
traversal(root.left, paths, res);
paths.remove(paths.size() - 1);// 回溯(这里一定是到了叶子结点的才触发回溯的(每一步都有回退的提前操作))
}
if (root.right != null) { // 右
traversal(root.right, paths, res);
paths.remove(paths.size() - 1);// 回溯(这里一定是到了叶子结点的才触发回溯的(每一步都有回退的提前操作))
}
}
}
直接递归法
每次都创建一个新的path。所以大家不共用,就不需要腾出空间来回溯。
class Solution {
List<String> result = new ArrayList<>();
public List<String> binaryTreePaths(TreeNode root) {
deal(root, "");
return result;
}
public void deal(TreeNode node, String s) {
if (node == null)
return;
if (node.left == null && node.right == null) {
result.add(new StringBuilder(s).append(node.val).toString());
return;
}
String tmp = new StringBuilder(s).append(node.val).append("->").toString();
deal(node.left, tmp);
deal(node.right, tmp);
}
}
🔥 发现了leetcode的新玩法
654. 最大二叉树
给定一个不重复的整数数组 nums
。 最大二叉树 可以用下面的算法从 nums
递归地构建:
- 创建一个根节点,其值为
nums
中的最大值。 - 递归地在最大值 左边 的 子数组前缀上 构建左子树。
- 递归地在最大值 右边 的 子数组后缀上 构建右子树。
返回 nums
构建的 最大二叉树 。
和上面的构建二叉树一样的思路
但是这里没必要用map来记录,因为最大值需要遍历,直接就将下标顺便获取了
class Solution {
public TreeNode constructMaximumBinaryTree(int[] nums) {
//这里没必要用map来记录,因为最大值需要遍历,直接就将下标顺便获取了
return FindMaxNode(nums,0,nums.length);
}
// 统一左闭右开区间
private TreeNode FindMaxNode(int[] nums, int begin, int end){
if(begin>=end)return null;
int max=Integer.MIN_VALUE;
int maxIndex=-1;
for(int i=begin;i<end;i++){
if(max<nums[i]){
max=nums[i];
maxIndex=i;
}
}
TreeNode root=new TreeNode(nums[maxIndex]);
root.left=FindMaxNode(nums,begin,maxIndex);
root.right=FindMaxNode(nums,maxIndex+1,end);
return root;
}
}
617. 合并二叉树
给你两棵二叉树: root1
和 root2
。
想象一下,当你将其中一棵覆盖到另一棵之上时,两棵树上的一些节点将会重叠(而另一些不会)。你需要将这两棵树合并成一棵新二叉树。合并的规则是:如果两个节点重叠,那么将这两个节点的值相加作为合并后节点的新值;否则,不为 null 的节点将直接作为新二叉树的节点。
返回合并后的二叉树。
注意: 合并过程必须从两个树的根节点开始。
这才是真正的递归!
目的就是求这一组数据单元t1、t2的情况,满足所有情况后就满足所有了。
返回的节点呀!注意一致鸭!
算法一定是简洁的
class Solution {
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
if (t1 == null) {
return t2;
} else if (t2 == null) {
return t1;
} else if(t1==null&&t2==null){
return null;
}
TreeNode merged = new TreeNode(t1.val + t2.val);
merged.left = mergeTrees(t1.left, t2.left);
merged.right = mergeTrees(t1.right, t2.right);
return merged;
}
}
📣非常感谢你阅读到这里,如果这篇文章对你有帮助,希望能留下你的点赞👍 关注❤收藏✅ 评论💬,大佬三连必回哦!thanks!!!
📚愿大家都能学有所得,功不唐捐!
👇下面是专栏彩蛋系列,你会喜欢的!(为了避免影响算法的简洁与优美,这里直接将之前的几十个专栏简化为3个部分,不过你点击开后发现惊喜。)👇
💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖
热门专栏
💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖💖