自己几个一直没搞懂的点:
在Java中,封装类 Integer
是为了将 int
这种基本数据类型转换为对象,使其具有对象的特性,例如可以存放在集合类中,也可以为其提供一些额外的方法。
-
- 集合类(如
ArrayList
、HashMap
)不能直接存储原始数据类型,只能存储对象。因此,如果你需要将int
存储在集合中,就需要使用Integer
对象。 - 例如,
ArrayList<Integer>
可以用来存储一组整数。
- 集合类(如
-
泛型:
- 泛型不能使用原始数据类型,只能使用对象。因此,如果你需要使用泛型来操作整数,就需要使用
Integer
而不是int
。
- 泛型不能使用原始数据类型,只能使用对象。因此,如果你需要使用泛型来操作整数,就需要使用
-
空值处理:
int
不能表示空值,因为它是原始数据类型,必须有一个值。原始数据类型int
的默认值是 0。Integer
可以为null
,因为它是一个对象,可以不被赋值。,而Integer
对象的默认值是null
。
-
性能差异:
- 原始数据类型
int
的操作通常更加高效,因为它不涉及对象的创建和管理。 - 对象类型
Integer
的操作可能涉及到装箱和拆箱的过程,可能在性能上略逊于原始数据类型。
- 原始数据类型
常用的集合类
-
List接口实现类:
ArrayList
: 基于动态数组实现的List,支持快速随机访问。LinkedList
: 基于双向链表实现的List,对于插入和删除操作较为高效。Vector
: 类似于ArrayList,但是是线程安全的,相对较少使用。
-
Set接口实现类:
HashSet
: 基于哈希表实现的Set,不保证元素的顺序。LinkedHashSet
: 在HashSet的基础上,保持了元素的插入顺序。TreeSet
: 基于红黑树实现的Set,保持元素的自然顺序或者通过比较器指定的顺序。
-
Queue和Deque接口实现类:
ArrayDeque
: 基于数组实现的双端队列。LinkedList
: 既实现了List接口,也实现了Deque接口,因此可以作为队列或双端队列使用。
-
Map接口实现类:
HashMap
: 基于哈希表实现的Map,不保证键值对的顺序。LinkedHashMap
: 在HashMap的基础上,保持了键值对的插入顺序。TreeMap
: 基于红黑树实现的Map,保持键的自然顺序或者通过比较器指定的顺序。
-
其他实用类:
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);
}
}
非递归法
import java.util.ArrayList;
import java.util.List;
import java.util.Stack;
public class Solution {
public List<Integer> preorderTraversal(TreeNode root) {
List<Integer> result = new ArrayList<>();
if (root == null) return result;
Stack<TreeNode> stack = new Stack<>();
stack.push(root);
while (!stack.isEmpty()) {
TreeNode node = stack.pop();
result.add(node.val);
if (node.right != null) {
stack.push(node.right);
}
if (node.left != null) {
stack.push(node.left);
}
}
return result;
}
}
class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) {
val = x;
}
}
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可以用于实现很多种数据结构
-
双向链表(Doubly Linked List):
LinkedList
内部实现了双向链表。每个节点包含数据元素以及对前一个节点和后一个节点的引用。这使得在链表中插入和删除元素的操作更加高效,因为只需要调整相邻节点的引用。
-
队列(Queue):
LinkedList
可以用作队列的实现。通过使用offer()
方法在链表末尾添加元素,并使用poll()
方法从链表的开头移除元素,可以实现队列的先进先出(FIFO)特性。
-
栈(Stack):
LinkedList
也可以用作栈的实现。通过使用push()
方法在链表的开头添加元素,并使用pop()
方法从链表的开头移除元素,可以实现栈的后进先出(LIFO)特性。
-
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);
}
}
}
二叉树的层序遍历二
但是注意队列的长度 sz
一定要在循环开始前保存下来,因为在循环过程中队列的长度是会变化的,不能直接用 q.size()
作为循环条件。
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;
}
}
此题收获:一定要在开始的时候进行判空,因为后面出现访问值可能出现访问不到的情况
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;
}
}
掌握了二叉树的层序遍历就可以解决它的最小深度,最大深度,
515. 在每个树行中找最大值 - 力扣(LeetCode)
注意返回空数组和null是不一样的
栈和队列的常用实现方法
对于队列,通常的选择是使用 LinkedList
或 ArrayDeque
。这两个类都实现了 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
。
ArrayDeque
:ArrayDeque
是基于数组实现的双端队列,可以高效地用作栈。它提供了标准的栈操作,如 push()
和 pop()
。使用 ArrayDeque
作为栈比使用旧的 Stack
类更高效,因为 Stack
是基于 Vector
实现的,而且是线程安全的,这会带来不必要的性能开销。
ArrayDeque
是实现栈和队列的首选方式,因为它提供了所需的灵活性和性能。同时,由于它是Java集合框架的一部分,因此与其他集合类型良好地集成。而 LinkedList
作为队列的选择更适合于那些需要频繁插入和删除元素的场景。
我们需要改变二叉树的结构, 本题中只为遍历这棵树,不断交换左右节点,因此遍历的顺序并不重要 重要的是交换的这个动作。用栈和队列都可以。
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;
}
}
翻转(或镜像)一棵二叉树意味着将树中的每个节点的左右子节点交换。这个过程可以通过递归或迭代的方式使用前序或后序遍历来实现,但中序遍历不适用于这种情况。下面我将解释为什么前序和后序遍历可以用于翻转二叉树,而中序遍历不行。
前序遍历实现翻转
在前序遍历中,我们按照“根-左-右”的顺序访问节点。翻转二叉树的前序遍历实现步骤如下:
- 访问当前节点(根节点),交换其左右子节点。
- 递归地对左子树进行翻转。
- 递归地对右子树进行翻转。
由于我们在处理根节点之前就已经交换了子节点,所以递归调用时仍然能正确地访问和交换所有子树的节点。
后序遍历实现翻转
后序遍历按照“左-右-根”的顺序访问节点。翻转二叉树的后序遍历实现步骤如下:
- 递归地对左子树进行翻转。
- 递归地对右子树进行翻转。
- 访问当前节点(根节点),交换其左右子节点。
在后序遍历中,我们先处理子节点,然后处理根节点。这意味着所有子树已经被翻转,我们只需要在最后交换根节点的子节点。
中序遍历不适用于翻转
中序遍历按照“左-根-右”的顺序访问节点。如果尝试使用中序遍历来翻转二叉树,会遇到一些问题:
- 当你访问并交换左子节点时,树的一部分已经被翻转。
- 然后当你访问根节点并试图交换其子节点时,你会再次翻转原来的左子树(现在是右子树),这实际上撤销了之前的翻转。
- 最后,当访问右子树(原来的左子树)时,会导致错误的翻转,因为树的结构在前面的步骤中已经被改变了。
由于这个原因,中序遍历不适合用于翻转二叉树。
递归做法(前序遍历二叉树)
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;
}
}
这道题要注意几件事:
- 一个是要求相等的是节点值,并非节点本身;
- 另一个是我们这里是用null来区分空结点,所以在判定条件t1.val==t2.val之前要判空;防止操作空指针比较空指针的数值;
- 本来想一个用栈一个用队列,让两棵子树的出节点顺序都相同,但是栈的结构无法实现层序遍历,都使用队列,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;
}
}
判断代码可以优化一下使之更加简洁
递归法:递归三部曲;
- 参数和返回值:进去的是根节点的左右子树,出来的是true或false;
- 递归结束的条件:共有四种情况:橙色返回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;
}