注:顺序是先筛选分类再按LeeCode上的通过率排的,每题最后的总结代码都是在LeeCode上跑过的,应该没啥问题。但是思路中的代码都是直接在CSDN编辑器里徒手敲的,若有笔误还烦请告知,蟹蟹~
立马写下如下三个词:
然后开始分析:
分析结束,将思路转为代码段在草稿纸上写:
终止条件:
if(root == null){
return 0;
}
递推关系:
depth = Math.max(maxDepth(root.left).maxDepth(root.right)) + 1;
返回值:
return dpeth;
整理成代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int maxDepth(TreeNode root) {
if(root == null){
return 0;
}
return Math.max(maxDepth(root.left),maxDepth(root.right)) + 1;
}
}
null
;终止条件:
if(root == null){
return null;
}
递推关系:
TreeNode tmp = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(tmp);
返回值:
return root;
整理成代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode mirrorTree(TreeNode root) {
if(root == null){
return null;
}
TreeNode tmp = root.left;
root.left = mirrorTree(root.right);
root.right = mirrorTree(tmp);
return root;
}
}
null
时返回,或者找到了第K大节点后返回;知识点:看到二叉搜索树想到中序遍历
终止条件:
if(root == null){
return;
}
if(count == 0){
return;
}
递推关系:
helper(root.right);
if(--count == 0){
res = root.val;
}
helper(root.left);
返回值:
//res直接写在类内上,所有方法共用,不用返回
整理成代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
private int count = 0,res = 0;
public int kthLargest(TreeNode root, int k) {
count = k;
helper(root);
return res;
}
private void helper(TreeNode root){
if(root == null){
return;
}
helper(root.right);
if(count == 0){
return;
}
if(--count == 0){
res = root.val;
}
helper(root.left);
}
}
开始分析:
此题需要用到层次遍历,层次遍历也可以被称为二叉树的广度优先遍历BFS,说道广度优先遍历,就要想到我们需要维护一个队列,通过控制节点在队列的进出来控制遍历的节奏。
二叉树BFS的大致框架,更多细节在具体实现的代码注释中有讲到:
//初始化一个队列并把首节点放进队列里
Queue<TreeNode> queue = new LinkedList<>(){{add(root);}};
//当队列不为空的时候循环
while(!queue.isEmpty()){
//一层层算账
for(int i = queue.size(); i > 0; i--){
//先把本层节点给poll出来
TreeNode node = queue.poll();
//再把与本层相连的下层左右节点给塞进去
if(node.left != null){
queue.add(node.left);
}
if(node.right != null){
queue.add(node.right);
}
}
}
知识点:广度优先,先建队列,每层poll出,下层add进。
因为这题打印结果呈现每层单独一个ArrayList的结果,所以要在每层的循环中开一个ArrayList接一下即可。总体框架还是能看出来的。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
//判断特殊情况
if(root == null){
return new ArrayList<>();
}
//一个ArrayList承载最后的结果
List<List<Integer>> res = new ArrayList<>();
//一个Queue->LinkedList承载每一层的节点,初始化为首层的root
Queue<TreeNode> queue = new LinkedList<>(){{add(root);}};
//若queue为空,则说明所有的节点都处理完毕,可以输出结果了
//这里用queue.isEmpty(),不能用queue==null
while(!queue.isEmpty()){
//缓存每层的数据,这里用List是为了每层直接接上最后的结果
List<Integer> tmp = new ArrayList<>();
//一个for,从queue.size()开始,这很关键,
//用for(int i = 0; i < queue.size(); i++)的话,则会报错
//因为队列的长度是在变化的,应该首先就指定好,这样才能保证每次循环只出一层的数据
for(int i = queue.size();i > 0; i--){
//队列先进先出,进是add出是poll
TreeNode father = queue.poll();
//记得先判断是否为空再添加进队列中,不然会报空指针异常
if(father.left != null){
//把左孩子加入进去,等待其在下一层被poll出来
queue.add(father.left);
}
//右孩子同理
if(father.right != null){
queue.add(father.right);
}
//把poll出的节点的值存进ArrayList中
tmp.add(father.val);
}
//把一层的ArrayList值存进最后的结果中
res.add(tmp);
}
return res;
}
}
先序遍历:根左右
中序遍历:左根右
1:前序遍历的首节点为root
int rootVal = preorder[0];
2:根据拿到的root去中序遍历里寻找对应的root
int rootIndex = 0;
for(int i = 0; i < inorder.length; i++){
if(rootVal == inorder[i]){
rootIndex = i;
break;
}
}
3-4:在中序遍历中找到root之后,它的左右即为当前root节点的左子树和右子树
//inorder的左边:
Arrays.copyOfRange(inorder, 0, rootIndex);
//inorder的右边:
Arrays.copyOfRange(inorder, rootIndex + 1, inorder.length);
注意copyOfRange的取值范围是左闭右开,可以画图观察,Java里很多这种取值的操作都是左闭右开的,想想str.substring(闭,开);
5-6:根据我们得到的左右子树,去前序遍历找左右子树的段落
//preorder的左边:
Arrays.copyOfRange(preorder, 1, 1 + rootIndex);
//preorder的右边:
Arrays.copyOfRange(preorder, 1 + rootIndex, preorder.length);
7-8:根据前序遍历的特点,我们所找的段落的首节点就是直接连着root的左孩子与右孩子
root.left = buildTree(preorder的左边,inorder的左边);
root.right = buildTree(preorder的右边,inorder的右边);
至此我们就可以进行递归操作了。
有没有发现树的递归操作很多时候都是在root.left和root.right的时候用等于接上的,这种格式可以多多考虑:
root.left = 递归左子树;
root.right = 递归右子树;
整理成代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode buildTree(int[] preorder, int[] inorder) {
int n = preorder.length;
if(n == 0){
return null;
}
int rootVal = preorder[0],rootIndex = 0;
for(int i = 0; i < n; i++){
if(rootVal == inorder[i]){
rootIndex = i;
break;
}
}
TreeNode root = new TreeNode(rootVal);
root.left = buildTree(Arrays.copyOfRange(preorder,1,1 + rootIndex),Arrays.copyOfRange(inorder,0,rootIndex));
root.right = buildTree(Arrays.copyOfRange(preorder,1+rootIndex,n),Arrays.copyOfRange(inorder,rootIndex+1,n));
return root;
}
}
null
时返回;终止条件:
if(root == null){
return null;
}
递推关系:
if(root.val > p.val && root.val > q.val){
return lowestCommonAncestor(root.left, p, q);
}else if(root.val < p.val && root.val < q.val){
return lowestCommonAncestor(root.right, p, q);
}
返回值:
return root;
整理成代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null){
return null;
}
if(root.val > p.val && root.val > q.val){
return lowestCommonAncestor(root.left, p, q);
}else if(root.val < p.val && root.val < q.val){
return lowestCommonAncestor(root.right, p, q);
}
return root;
}
}
root == null
的时候返回null
,root == p
的时候返回p
,root == q
的时候返回q
null
说明两个都在右边,当右子树为null
说明两个都在左边,只有左右子树都不为null
的时候该root才是公共祖先,返回这个root,层层上传。终止条件
if(root == null){
return null;
}
if(root == p || root == q){
return root;
}
递推关系
TreeNode leftNode = lowestCommonAncestor(root.left,p,q);
TreeNode rightNode = lowestCommonAncestor(root.right,p,q);
if(leftNode == null){
return rightNode;
}
if(rightNode == null){
return leftNode;
}
返回值
return root;
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if(root == null){
return null;
}
if(root == p || root == q){
return root;
}
TreeNode leftNode = lowestCommonAncestor(root.left,p,q);
TreeNode rightNode = lowestCommonAncestor(root.right,p,q);
if(leftNode == null){
return rightNode;
}
if(rightNode == null){
return leftNode;
}
return root;
}
}
这题为啥会比32-II做出来的人少…我蒙了,就是广度优先遍历的板子就完事了。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public int[] levelOrder(TreeNode root) {
//特殊边界要提前考虑到
if(root == null){
return new int[0];
}
LinkedList<TreeNode> queue = new LinkedList<>(){{add(root);}};
ArrayList<Integer> res = new ArrayList<>();
while(!queue.isEmpty()){
for(int i = queue.size(); i > 0; i--){
TreeNode tmp = queue.poll();
res.add(tmp.val);
if(tmp.left != null){
queue.add(tmp.left);
}
if(tmp.right != null){
queue.add(tmp.right);
}
}
}
//这里返回值是int[],再遍历一遍传过去吧,卑微
int[] n = new int[res.size()];
for(int j = 0; j < res.size(); j++){
n[j] = res.get(j);
}
return n;
}
}
你要是一路看下来的话这玩意是第三个BFS题了,加个reverse就完事了。这题我们来唠唠返回值的事,这个方法让我们返回的是List
这么个玩意,当>
root == null
的时候,我们应该怎么看返回个啥呢?答案是看它最外层是啥,它最外层是个List<>
,那我们就
if(root == null){
return ArrayList<>();
//return LinkedList<>();一样也行
}
这玩意说难也不难,就是有时候会一下钻牛角尖里,不造返回啥了,写出来防止忘记。
还有一个好玩的操作是判断奇偶数,这么写其实也不会快(因为听说你用取余的时候,JAVA底层已经优化成这种写法了),但是秀呀哈哈。
//奇数
if((i & 1) == 1){
...
}
//偶数
if((i & 1) == 0){
...
}
最后上个代码:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public List<List<Integer>> levelOrder(TreeNode root) {
if(root == null){
return new ArrayList<>();
}
LinkedList<List<Integer>> res = new LinkedList<>();
LinkedList<TreeNode> queue = new LinkedList<>(){{add(root);}};、
//设置个层数
int floor = 0;
while(!queue.isEmpty()){
ArrayList<Integer> tmp = new ArrayList<>();
for(int i = queue.size(); i > 0; i--){
TreeNode node = queue.poll();
tmp.add(node.val);
if(node.left != null){
queue.add(node.left);
}
if(node.right!= null){
queue.add(node.right);
}
}
//当奇数层时需要翻转
if((floor & 1) == 1){
Collections.reverse(tmp);
}
res.add(tmp);
//层数++
floor++;
}
return res;
}
}
emmm这题写的有点丑,虽然最后蜜汁跑出了99.96%和100%的成绩…
思路就是递归,感觉不用之前那么一板一眼的分析了,有点感觉了,就直接说我怎么想的吧。首先拿到左边和右边的高度,两个一减取个绝对值,要是这个值大于1呢就说明刚好在你正在处理的root节点完球了,返回false
就完事了,不然呢,就返回左子树有没有完球 && 右子树有没有完球
。还有一个问题是我们怎么知道左边和右边的高度是多少呢?再写个函数,像“55-1 二叉树的深度”那题那样递归一下就完事了。
所以我写了两个递归咋跑到99.96%的?我看大佬们写的比我简洁多了。
我好像发现了做递归题的idea:
你:算法之神,你只要告诉我前一次_______,我就能给你整出我这个________。
算法之神:小伙子牛逼哦,那我给你前一次__________的结果,你算吧。
你:现在只要把前一次_________xxxxx,然后xxxxx,最后返回的_______就是我这个________了,牛逼不。
算法之神:牛逼哦~
你:算法之神,你只要告诉我前一个节点是不是平衡二叉树,我就能给你整出我这个节点是不是平衡二叉树。
算法之神:小伙子牛逼哦,那我给你前一个节点是不是平衡二叉树的结果,你算吧。
你:现在只要看左边是不是平衡二叉树,再看右边是不是平衡二叉树,要是两边都平衡,那我这个节点就是平衡二叉树,要是有一边不是,那我这个节点就不是平衡二叉树。牛逼不。
算法之神:牛逼哦~
顺便记一下绝对值方法:
Math.abs(i);
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isBalanced(TreeNode root) {
//空树肯定平衡
if(root == null){
return true;
}
//拿左右子树高度
int leftNode = treeHeight(root.left);
int rightNode = treeHeight(root.right);
//瞅瞅自己这个节点上两个差有没有大于1,大于了就麻溜的false
if(Math.abs((leftNode - rightNode)) > 1){
return false;
}
//瞅瞅左边,瞅瞅右边
return isBalanced(root.left) && isBalanced(root.right);
}
public int treeHeight(TreeNode root){
//空了高度就是0了
if(root == null){
return 0;
}
//左边高度和右边高度拿到
int leftHeight = treeHeight(root.left);
int rightHeight = treeHeight(root.right);
//现在的高度就是左右高度高的那嘎达加1
return Math.max(leftHeight,rightHeight) + 1;
}
}
镜像就是左边的左边等于右边的右边,左边的右边等于右边的左边。
我们造一个递归方法,表示该树是否镜像,只要告诉我左边的左边和右边的右边是不是镜像,左边的右边和右边的左边是不是镜像,我就能告诉你当前节点是不是镜像,我只要再判断当前节点的左右是一样的即可。
判断左右节点一样之前要保证他们都不是null或者都是null,不然会报空指针,用这样的模板即可:
//先判断左右是不是都是null
if(left == null && right == null){
return true;
}
//已经排除左右都是null的情况下,任意一边为null都说明left和right不相等
if(left == null || right == null){
return false;
}
代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSymmetric(TreeNode root) {
if(root == null){
return true;
}
return helper(root.left,root.right);
}
private boolean helper(TreeNode left,TreeNode right){
if(left == null && right == null){
return true;
}
if(left == null || right == null){
return false;
}
//除去null,还有一种false是左右不相等
if(left.val != right.val){
return false;
}
//算法之神告诉了我左左右右和左右右左是不是镜像
//如果左右相等,那本节点的结果就是 左左右右 && 左右右左 的结果
return helper(left.left,right.right) && helper(left.right,right.left);
}
}
这题是传说中的回溯(么?我也不知道),我死在浅拷贝还是深拷贝上了,绝望调试了半天…这地方是个深坑,没踩过十有八九是会忽略的,这题就来唠唠这玩意。
先说整体思路吧,一个List
来接最后的结果,一个>
List
来接(这里插一句,看我顶上一开始申明的LinkedList
就知道我想拿栈来做这玩意了,结果发现栈做出来结果是倒序的它不认,加个Collections.reverse()
直接给我干掉2ms,拉倒吧还是用队列做吧。),然后就是一个递归函数。
唠唠这个递归函数,首先碰到null返回,其次在本节点中的任务是先把本节点的值加在统计本条线路的值中,然后把本节点塞进队列里,再去查看本节点是不是叶子,如果是叶子的话就看和我们想要的值是否相等,相等就深拷贝一下我们记录的这条线路,然后把本节点的值减掉,把本节点吐出来;要不是叶子节点就向左向右递归,注意递归一路弹回来到这之后还是要把本节点吐出来的,所以不如直接把吐出来放在最后,少打一句话舒坦三十秒。
提一嘴这个函数,用队列的时候可以用上:
LinkedList<Integer> linkedlist = new LinkedList<>();
linkedlist.removeLast(); //删除队列的最后一个元素
先看代码再说坑,代码如下:
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
List<List<Integer>> res;
LinkedList<Integer> ways;
int sum = 0;
public List<List<Integer>> pathSum(TreeNode root, int sum) {
if(root == null){
return new ArrayList<>();
}
//前期准备
res = new ArrayList<>();
ways = new LinkedList<>();
this.sum = sum;
//调用递归方法
helper(root,0);
return res;
}
private void helper(TreeNode root,int waySum){
if(root == null){
return;
}
//本节点到了,先给他把值加上,塞队里
waySum += root.val;
ways.add(root.val);
//瞅瞅本节点是不是叶子节点啊,是叶子的话总值对不对啊
if(waySum == sum && root.left == null && root.right == null){
//深拷贝的牌面还是要有的,把当前路径深拷贝到结果队列里
res.add(new LinkedList<>(ways));
}else{
//不是叶子,下面还有节点,药不能停,向左向右继续
helper(root.left,waySum);
helper(root.right,waySum);
}
//把最后一位移除收尾,对于叶子来说是把叶子移出;
//对于非叶子来说是递归结束的时候弹到本节点时把本节点移出
ways.removeLast();
}
}
然后就要说拷贝这事了,我一开始在叶子节点那是这么写的:
if(waySum == sum && root.left == null && root.right == null){
res.add(ways);
}
然后就麻了,得到的结果是这样的:
[[],[]]
我寻思找出来路径的个数也对了啊,怎么不显示路径呢,我也add(ways)
了啊。最后发现我这样写是浅拷贝,拷贝的只是指向ways
的一个指针,所以ways
一会add(root.val)
的一会removeLast()
的,我想输出的值也在跟着变了。
浅拷贝(Shallow Copy):
①对于数据类型是基本数据类型的成员变量,浅拷贝会直接进行值传递,也就是将该属性值复制一份给新的对象。因为是两份不同的数据,所以对其中一个对象的该成员变量值进行修改,不会影响另一个对象拷贝得到的数据。
②对于数据类型是引用数据类型的成员变量,比如说成员变量是某个数组、某个类的对象等,那么浅拷贝会进行引用传递,也就是只是将该成员变量的引用值(内存地址)复制一份给新的对象。因为实际上两个对象的该成员变量都指向同一个实例。在这种情况下,在一个对象中修改该成员变量会影响到另一个对象的该成员变量值。
所以这时候要深拷贝一下,直接新建一个LinkedList<>()
然后把ways
里的值拷贝过去就完事了。
if(waySum == sum && root.left == null && root.right == null){
res.add(new LinkedList<>(ways));
}
深拷贝(Deep Copy):
相对于浅拷贝而言,对于引用类型的修改,并不会影响到对应的copy对象的值。
每一层的每个对象都进行浅拷贝=深拷贝。
来做个总结:
以后出现结果个数正确、内容错误或消失的时候,可以考虑是否是浅拷贝导致的。
思路是用BFS做序列化和反序列化,主要还是细心,很多小细节调了不少次,还有就是Sting和Integer的转换注意下,最后注意一下String判相等还是用equals()
吧,怎么着都方便点。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
public class Codec {
// Encodes a tree to a single string.
public String serialize(TreeNode root) {
if(root == null){
return "";
}
Queue<TreeNode> queue = new LinkedList<>(){{add(root);}};
StringBuilder sb = new StringBuilder();
sb.append("[");
while(!queue.isEmpty()){
for(int i = queue.size(); i > 0; --i){
TreeNode tmp = queue.poll();
if(tmp != null){
sb.append(tmp.val + ",");
queue.add(tmp.left);
queue.add(tmp.right);
}else{
sb.append("null,");
}
}
}
sb.append("]");
return sb.toString();
}
// Decodes your encoded data to tree.
public TreeNode deserialize(String data) {
if(data == "" ){
return null;
}
data = data.substring(1,data.length() - 1);
String[] strs = data.split(",");
int index = 0;
TreeNode root = new TreeNode(Integer.parseInt(strs[0]));
Queue<TreeNode> queue = new LinkedList<>(){{add(root);}};
index++;
while(!queue.isEmpty()){
TreeNode node = queue.poll();
if(!strs[index].equals("null")){
node.left = new TreeNode(Integer.parseInt(strs[index]));
queue.add(node.left);
}
index++;
if(!strs[index].equals("null")){
node.right = new TreeNode(Integer.parseInt(strs[index]));
queue.add(node.right);
}
index++;
}
return root;
}
}
// Your Codec object will be instantiated and called as such:
// Codec codec = new Codec();
// codec.deserialize(codec.serialize(root));
首先需要递归的是从本层节点开始,是否有B树的子结构,这个需要一个对比是否是子结构的递归,然后我们还需要一个递归是从本层节点的左右节点开始,是否有B树的子结构,所以这题是双重递归。
/**
* Definition for a binary tree node.
* public class TreeNode {
* int val;
* TreeNode left;
* TreeNode right;
* TreeNode(int x) { val = x; }
* }
*/
class Solution {
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A == null || B == null){
return false;
}
//递归A的节点
return helper(A,B) || isSubStructure(A.left,B) || isSubStructure(A.right,B);
}
//递归A某节点向下的树与B树是否一致
private boolean helper(TreeNode A, TreeNode B){
if(B == null){
return true;
}
if(A == null){
return false;
}
return A.val == B.val && helper(A.left,B.left) && helper(A.right,B.right);
}
}
到此为止树的部分就结束啦~求个关注啵啵啵(●´З`●)