代码随想录一刷Day7 二叉树

自己几个一直没搞懂的点

在Java中,封装类 Integer 是为了将 int 这种基本数据类型转换为对象,使其具有对象的特性,例如可以存放在集合类中,也可以为其提供一些额外的方法。

    • 集合类(如 ArrayListHashMap)不能直接存储原始数据类型,只能存储对象。因此,如果你需要将 int 存储在集合中,就需要使用 Integer 对象。
    • 例如,ArrayList<Integer> 可以用来存储一组整数。
  1. 泛型

    • 泛型不能使用原始数据类型,只能使用对象。因此,如果你需要使用泛型来操作整数,就需要使用 Integer 而不是 int
  2. 空值处理

    • int 不能表示空值,因为它是原始数据类型,必须有一个值。原始数据类型 int 的默认值是 0。
    • Integer 可以为 null,因为它是一个对象,可以不被赋值。,而 Integer 对象的默认值是 null
  3. 性能差异

    • 原始数据类型 int 的操作通常更加高效,因为它不涉及对象的创建和管理。
    • 对象类型 Integer 的操作可能涉及到装箱和拆箱的过程,可能在性能上略逊于原始数据类型。

 常用的集合类

  1. List接口实现类

    • ArrayList: 基于动态数组实现的List,支持快速随机访问。
    • LinkedList: 基于双向链表实现的List,对于插入和删除操作较为高效。
    • Vector: 类似于ArrayList,但是是线程安全的,相对较少使用。
  2. Set接口实现类

    • HashSet: 基于哈希表实现的Set,不保证元素的顺序。
    • LinkedHashSet: 在HashSet的基础上,保持了元素的插入顺序。
    • TreeSet: 基于红黑树实现的Set,保持元素的自然顺序或者通过比较器指定的顺序。
  3. Queue和Deque接口实现类

    • ArrayDeque: 基于数组实现的双端队列。
    • LinkedList: 既实现了List接口,也实现了Deque接口,因此可以作为队列或双端队列使用。
  4. Map接口实现类

    • HashMap: 基于哈希表实现的Map,不保证键值对的顺序。
    • LinkedHashMap: 在HashMap的基础上,保持了键值对的插入顺序。
    • TreeMap: 基于红黑树实现的Map,保持键的自然顺序或者通过比较器指定的顺序。
  5. 其他实用类

    • HashSet, LinkedHashSet, TreeSet: 实现了Set接口,用于存储不重复的元素。
    • HashMap, LinkedHashMap, TreeMap: 实现了Map接口,用于存储键值对。
    • Stack: 基于数组实现的栈。
    • PriorityQueue: 优先级队列,用于按照优先级顺序处理元素。

二叉树的前中后序遍历

递归遍历主要是确定四点:什么进去,进去做什么,什么出来,何时出来

 前序遍历

/**
 * Definition for a binary tree node.
 * public class TreeNode {
 *     int val;
 *     TreeNode left;
 *     TreeNode right;
 *     TreeNode() {}
 *     TreeNode(int val) { this.val = val; }
 *     TreeNode(int val, TreeNode left, TreeNode right) {
 *         this.val = val;
 *         this.left = left;
 *         this.right = right;
 *     }
 * }
 */
class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        List<Integer> result=new ArrayList<Integer>();
        Preorder(root,result);
        return result;
    }
    public void Preorder(TreeNode root,List<Integer> result)//什么进去 什么出来
    {
        if(root==null)
        return;//什么时候停止
        result.add(root.val);//进去做什么
        Preorder(root.left,result);
        Preorder(root.right,result);
    }
}

非递归法

class Solution {
    public List<Integer> preorderTraversal(TreeNode root) {
        TreeNode pcur=root;
        List<Integer> res = new ArrayList<Integer>();
        Stack<TreeNode> stack=new Stack<TreeNode>();
        while(pcur!=null||!stack.isEmpty())
        {
            while(pcur!=null)
            {
                res.add(pcur.val);
                stack.push(pcur);
                pcur=pcur.left;
            }
            if(!stack.isEmpty())
            {
                //入了栈的已经进行了访问
                pcur=stack.pop();
                pcur=pcur.right;
            }
        }
        return res;
    }

中序遍历

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result=new ArrayList<Integer>();
        Postorder(root,result);
        return result;
    }
    public void Postorder(TreeNode root,List<Integer> result)
    {
        if(root==null)
        {
            return;
        }
        Postorder(root.left,result);
        result.add(root.val);
        Postorder(root.right,result);
    }
}

 非递归

class Solution {
    public List<Integer> inorderTraversal(TreeNode root) {
        List<Integer> res = new ArrayList<Integer>();
        Stack<TreeNode> stack=new Stack<TreeNode>();
        TreeNode pcur=root;
        // visit(res,root);
        while(!stack.isEmpty()||pcur!=null)
        {
            while(pcur!=null)
            {
                stack.push(pcur);
                pcur=pcur.left;
            }
            if(!stack.isEmpty())
            {
                pcur=stack.pop();
                res.add(pcur.val);
                pcur=pcur.right;
            }
        }
        return res;
    }}

后序遍历

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result=new ArrayList<Integer>();
        Postorder(root,result);
        return result;
    }
    public void Postorder(TreeNode root,List<Integer> result)
    {
        if(root==null)
        {
            return;
        }
        Postorder(root.left,result);
        Postorder(root.right,result);
        result.add(root.val);
    }
}

非递归法

class Solution {
    public List<Integer> postorderTraversal(TreeNode root) {
        List<Integer> result=new ArrayList<Integer>();
        Stack<TreeNode> stack=new Stack<TreeNode>();
        TreeNode pcur=root;
        TreeNode pre=root;
        while(pcur!=null||!stack.isEmpty())
        {
            while(pcur!=null)
            {
                //与前序不同,这里我们只遍历不做访问操作,因为一个由左右孩子的节点需要在最后才能访问
                stack.push(pcur);
                pcur=pcur.left;
            }
            //前面是while 因此stack会一直向左遍历直到为空
            if(!stack.isEmpty())
            {
                pcur=stack.peek();//指向栈顶元素
                if(pcur.right==null||pcur.right==pre)
                {
                  result.add(pcur.val);//没有未访问的左右孩子 访问该节点
                    stack.pop();//访问完毕右节点,我们应当返回上一层,但是要防止再次判定右节点不为空 就要进行如下两步操作
                    pre=pcur;
                    pcur=null;//这一步是为了进入上面的while
                }else{
                    
                     pcur=pcur.right;
                }

            }
        }
        return result;
    }
}

LinkedList可以用于实现很多种数据结构

  1. 双向链表(Doubly Linked List):

    • LinkedList 内部实现了双向链表。每个节点包含数据元素以及对前一个节点和后一个节点的引用。这使得在链表中插入和删除元素的操作更加高效,因为只需要调整相邻节点的引用。
  2. 队列(Queue):

    • LinkedList 可以用作队列的实现。通过使用 offer() 方法在链表末尾添加元素,并使用 poll() 方法从链表的开头移除元素,可以实现队列的先进先出(FIFO)特性。
  3. 栈(Stack):

    • LinkedList 也可以用作栈的实现。通过使用 push() 方法在链表的开头添加元素,并使用 pop() 方法从链表的开头移除元素,可以实现栈的后进先出(LIFO)特性。
  4. Deque(双端队列):

    • LinkedList 实现了 Deque 接口,支持双端队列的操作。这包括在队列的两端添加和移除元素。

二叉树的层序遍历法

1.递归法
class Solution {
     List<List<Integer>> result=new ArrayList<List<Integer>>();
    public List<List<Integer>> levelOrder(TreeNode root) {
      check1(root,0);
     return result;
    }
public void check1(TreeNode node,int deep)
    {
        if(node==null) return;
        deep++;
           if (result.size() < deep) {
            //当层级增加时,list的Item也增加,利用list的索引值进行层级界定
            //如果没有这个判定条件,递归调用将在每一层都创建一个新的列表,而不会检查该层级是否已经存在于 resList 中
             List<Integer> item=new ArrayList<Integer>();
             result.add(item);//item此时是一个空列表
        }
       
        result.get(deep-1).add(node.val);

        check1(node.left,deep);
        check1(node.right,deep);
    }
2. 队列法
 public void check2(TreeNode node)
    {
        if(node==null) return;
        Queue<TreeNode> que=new LinkedList<TreeNode>();
        que.offer(node);

        while(!que.isEmpty())
        {
            //每一次循环都会重新声明itemList
            List<Integer> itemlist=new ArrayList<Integer>();
            int len=que.size();//每一层的遍历中,que只储存本层和下层的结点 这里的len相当于本层结点的快照

            while(len>0)//这一层有多少节点
            {
                TreeNode tmpnode=que.poll();
                itemlist.add(tmpnode.val);

                if(tmpnode.left!=null) que.offer(tmpnode.left);
                if(tmpnode.right!=null) que.offer(tmpnode.right);
                len--;
            }

            result.add(itemlist);
        }
        
    }
}

二叉树的层序遍历二

class Solution {
    List<List<Integer>> reslut =new ArrayList<List<Integer>>();
        
    public List<List<Integer>> levelOrderBottom(TreeNode root) {
        check(root);
        reslut=reverse(reslut);
        return reslut;
    }
    public void check(TreeNode node)
    {
        if(node==null) return;
         Queue<TreeNode> que=new LinkedList<TreeNode>();
         que.offer(node);

         while(!que.isEmpty())
         {
             int len=que.size();
            List<Integer> itemList=new ArrayList<Integer>();
              while(len>0)
              {
                  TreeNode tmp=que.poll();


                  itemList.add(tmp.val);
              if(tmp.left!=null) que.offer(tmp.left);
              if(tmp.right!=null) que.offer(tmp.right); 
              len--;
              }

              reslut.add(itemList);
             
         }
    }
    public List<List<Integer>> reverse(List<List<Integer>> reslut)
    {
        List<List<Integer>> reslut2 =new ArrayList<List<Integer>>();
        int j=0;
        for(int i=reslut.size()-1;i>=0;i--)
        {
            reslut2.add(reslut.get(i));
        }
        return reslut2;
    }
}

199. 二叉树的右视图 - 力扣(LeetCode)

此题收获:一定要在开始的时候进行判空,因为后面出现访问值可能出现访问不到的情况

class Solution {
    public List<Integer> rightSideView(TreeNode root) {
        List<Integer> result=new ArrayList<Integer>();
        if(root==null) return result;
        //用队列来解决
        Queue<TreeNode> que=new LinkedList<>();
        que.offer(root);
        while(!que.isEmpty())
        {
            int len=que.size();
            while(len>=1)
            {
                len--;
                TreeNode tmp=que.poll();
                if(len==0)//此时处理本层最后一个元素
                {
                    result.add(tmp.val);
                }
                if(tmp.left!=null) que.offer(tmp.left);
                if(tmp.right!=null) que.offer(tmp.right);
            }

        }
        return result;
    }
}

掌握了二叉树的层序遍历就可以解决它的最小深度,最大深度,

111. 二叉树的最小深度 - 力扣(LeetCode)

637. 二叉树的层平均值 - 力扣(LeetCode)

515. 在每个树行中找最大值 - 力扣(LeetCode)

注意返回空数组和null是不一样的

栈和队列的常用实现方法

对于队列,通常的选择是使用 LinkedListArrayDeque。这两个类都实现了 Deque 接口,支持双端队列操作,但通常用作队列。

Queue<Integer> queue = new LinkedList<>();
queue.offer(1);
queue.offer(2);
int first = queue.poll(); // 返回 1
Queue<Integer> queue = new ArrayDeque<>();
queue.offer(1);
queue.offer(2);
int first = queue.poll(); // 返回 1

对于,推荐的实现是使用 ArrayDeque

ArrayDequeArrayDeque 是基于数组实现的双端队列,可以高效地用作栈。它提供了标准的栈操作,如 push()pop()。使用 ArrayDeque 作为栈比使用旧的 Stack 类更高效,因为 Stack 是基于 Vector 实现的,而且是线程安全的,这会带来不必要的性能开销。

ArrayDeque 是实现栈和队列的首选方式,因为它提供了所需的灵活性和性能。同时,由于它是Java集合框架的一部分,因此与其他集合类型良好地集成。而 LinkedList 作为队列的选择更适合于那些需要频繁插入和删除元素的场景。

z226. 翻转二叉树 - 力扣(LeetCode)

我们需要改变二叉树的结构,   本题中只为遍历这棵树,不断交换左右节点,因此遍历的顺序并不重要 重要的是交换的这个动作。用栈和队列都可以。

class Solution {
    public TreeNode invertTree(TreeNode root) {
        //本题中只为遍历这棵树,不断交换左右节点,因此遍历的顺序并不重要 重要的是交换的这个动作
        if(root==null) return root;
        Stack<TreeNode> stack=new Stack<>();
        stack.push(root);
        while(!stack.isEmpty())
        {
            // int length=stack.size();
            // for(int i=0;i<length;i++)
            // {
              TreeNode tmp=stack.pop();
              TreeNode t=tmp.left;
              tmp.left=tmp.right;
              tmp.right=t;
            if(tmp.left!=null) stack.push(tmp.left);
            if(tmp.right!=null) stack.push(tmp.right);
            // }
            
        }
        return root;
    }
}

翻转(或镜像)一棵二叉树意味着将树中的每个节点的左右子节点交换。这个过程可以通过递归或迭代的方式使用前序或后序遍历来实现,但中序遍历不适用于这种情况。下面我将解释为什么前序和后序遍历可以用于翻转二叉树,而中序遍历不行。

前序遍历实现翻转

在前序遍历中,我们按照“根-左-右”的顺序访问节点。翻转二叉树的前序遍历实现步骤如下:

  1. 访问当前节点(根节点),交换其左右子节点。
  2. 递归地对左子树进行翻转。
  3. 递归地对右子树进行翻转。

由于我们在处理根节点之前就已经交换了子节点,所以递归调用时仍然能正确地访问和交换所有子树的节点。

后序遍历实现翻转

后序遍历按照“左-右-根”的顺序访问节点。翻转二叉树的后序遍历实现步骤如下:

  1. 递归地对左子树进行翻转。
  2. 递归地对右子树进行翻转。
  3. 访问当前节点(根节点),交换其左右子节点。

在后序遍历中,我们先处理子节点,然后处理根节点。这意味着所有子树已经被翻转,我们只需要在最后交换根节点的子节点。

中序遍历不适用于翻转

中序遍历按照“左-根-右”的顺序访问节点。如果尝试使用中序遍历来翻转二叉树,会遇到一些问题:

  1. 当你访问并交换左子节点时,树的一部分已经被翻转。
  2. 然后当你访问根节点并试图交换其子节点时,你会再次翻转原来的左子树(现在是右子树),这实际上撤销了之前的翻转。
  3. 最后,当访问右子树(原来的左子树)时,会导致错误的翻转,因为树的结构在前面的步骤中已经被改变了。

由于这个原因,中序遍历不适合用于翻转二叉树。

递归做法(前序遍历二叉树)

class Solution {
    public TreeNode invertTree(TreeNode root) {
        //
        if(root==null) return root;
        swap(root);
        invertTree(root.left);
        invertTree(root.right);
        return root;
    }
    public void swap(TreeNode root)
    {
        TreeNode tmp=root.left;
        root.left=root.right;
        root.right=tmp;
        return;
    }
}

101. 对称二叉树 - 力扣(LeetCode)

这道题要注意几件事:

  1. 一个是要求相等的是节点值,并非节点本身;
  2. 另一个是我们这里是用null来区分空结点,所以在判定条件t1.val==t2.val之前要判空;防止操作空指针比较空指针的数值;
  3. 本来想一个用栈一个用队列,让两棵子树的出节点顺序都相同,但是栈的结构无法实现层序遍历,都使用队列,t1的子节点逆序入队列即可;

队列层序遍历法:


class Solution {
    public boolean isSymmetric(TreeNode root) {
        Deque<TreeNode> que1=new LinkedList<>();
        Deque<TreeNode> que2=new LinkedList<>();
        if(root==null)
        return true;
        que1.offer(root.left);
        que2.offer(root.right);
        while(!que1.isEmpty()&&!que2.isEmpty())
        {
            TreeNode t1=que1.poll();
            TreeNode t2=que2.poll();
           //空结点以空的形式加入,就要防止因为弹出的节点为空val值取不到的情况
            if(t1==null&&t2==null)
            continue;
            else if(t1==null&&t2!=null)
            {
                return false;
            }else if(t1!=null&&t2==null)
            {
                return false;
            }
            if(t1.val==t2.val)
            {
                //t1要反着放
                if(t1.right!=null)
                que1.offer(t1.right);
                else{
                    que1.offer(null);
                }
                if(t1.left!=null)
                que1.offer(t1.left);
                else{
                     que1.offer(null);
                }
                if(t2.left!=null)
                que2.offer(t2.left);
                else{
                    que2.offer(null);
                }
                if(t2.right!=null)
                que2.offer(t2.right);
                else{
                    que2.offer(null);
                }
                 continue;
            }
            else{
                return false;
            }
        }
        return true;
    }
}

判断代码可以优化一下使之更加简洁 

 递归法:递归三部曲;

  1. 参数和返回值:进去的是根节点的左右子树,出来的是true或false;
  2. 递归结束的条件:共有四种情况:橙色返回false,绿色返回true(还是要注意不要操作空指针)
  • l=r=null;
  • l=null r!=null;
  • l!=null,r==null;
  • l!=null r!=null l==r;

        3.单层进行的操作 :内侧和内侧比,也就是left.right和right.left比,外侧和外侧比,也就是left.left和right.right;递归下去,如果内外都对称就返回true ,有一侧不对称就返回false;

    public boolean isSymmetric1(TreeNode root) {
        return compare(root.left, root.right);
    }

    private boolean compare(TreeNode left, TreeNode right) {

        if (left == null && right != null) {
            return false;
        }
        if (left != null && right == null) {
            return false;
        }

        if (left == null && right == null) {
            return true;
        }
        if (left.val != right.val) {
            return false;
        }
        // 比较外侧
        boolean compareOutside = compare(left.left, right.right);
        // 比较内侧
        boolean compareInside = compare(left.right, right.left);
        return compareOutside && compareInside;
    }

如果只使用一个栈的话,其实是把左右两个子树要比较的元素顺序放进一个容器,然后成对成对的取出来进行比较,那么其实使用栈也是可以的。

 public boolean isSymmetric3(TreeNode root) {
        Queue<TreeNode> deque = new LinkedList<>();
        deque.offer(root.left);
        deque.offer(root.right);
        while (!deque.isEmpty()) {
            TreeNode leftNode = deque.poll();
            TreeNode rightNode = deque.poll();
            if (leftNode == null && rightNode == null) {
                continue;
            }
//            if (leftNode == null && rightNode != null) {
//                return false;
//            }
//            if (leftNode != null && rightNode == null) {
//                return false;
//            }
//            if (leftNode.val != rightNode.val) {
//                return false;
//            }
            // 以上三个判断条件合并
            if (leftNode == null || rightNode == null || leftNode.val != rightNode.val) {
                return false;
            }
            // 这里顺序与使用Deque不同
            deque.offer(leftNode.left);
            deque.offer(rightNode.right);
            deque.offer(leftNode.right);
            deque.offer(rightNode.left);
        }
        return true;
    }

  • 22
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值