在做题之前说明
Deque
和Queue
在Java中,
Deque
和Queue
是两种不同的数据结构接口,它们都继承自Collection
接口;Deque
是Queue
的超集,提供了更多的操作和灵活性,以下它们之间存在一些关键的区别:
-
操作范围:
Queue
接口仅定义了一端(队尾)用于插入元素,另一端(队首)用于删除元素的操作,这符合先进先出(FIFO)的原则。Deque
接口则扩展了Queue
的能力,允许在双端进行插入和删除元素,因此它不仅支持FIFO原则,还可以支持后进先出(LIFO)原则,类似于栈。
-
具体实现:
Queue
接口的典型实现是LinkedList
和PriorityQueue
。其中,LinkedList
实现了所有Queue
接口的方法,同时还支持Deque
接口的方法。而PriorityQueue
则是一个基于优先级堆的无界队列,它不支持Deque
接口。Deque
接口的实现包括ArrayDeque
和LinkedList
。ArrayDeque
是基于数组的双端队列实现,通常比LinkedList
更高效,特别是在随机访问和尾部操作方面。LinkedList
也实现了Deque
接口,因此它既可以作为队列使用,也可以作为双端队列使用。
-
方法差异:
Queue
接口提供的方法如offer(E e)
用于添加元素,poll()
用于移除并返回队首元素,peek()
用于查看队首元素而不移除。Deque
接口除了包含Queue
的所有方法外,还提供了额外的方法,如addFirst(E e)
和addLast(E e)
用于在双端分别添加元素,removeFirst()
和removeLast()
用于从双端移除元素,以及getFirst()
和getLast()
用于获取双端的元素而不移除。
-
性能考虑:
- 对于需要频繁在两端插入和删除元素的场景,
Deque
通常是更好的选择,因为它专门为此设计。 - 如果应用场景只需要队列的基本操作(即只有一端添加,另一端删除),那么使用
Queue
可能更为简洁,尤其是当使用PriorityQueue
实现时,它提供了特定的排序机制。
- 对于需要频繁在两端插入和删除元素的场景,
offer()
方法与offerLast()
Deque
接口中的offer()
方法与offerLast()
方法都用于向双端队列的末尾添加元素,但它们之间有细微的差别。
offer()
方法是Queue
接口中定义的,它被所有实现了Queue
接口的类继承,包括Deque
。当你调用offer()
方法时,它会将元素添加到Deque
的末尾。如果Deque
已经满了,offer()
方法将抛出一个IllegalStateException
。而offerLast()
方法是Deque
接口特有的,在Deque
已满时不会抛出异常,而是返回一个特殊的值false
,表明元素没有被添加到队列中。这种行为使得offerLast()
方法在某些情况下更加健壮,因为它避免了因队列满而导致的程序异常。
peek()
和peekFirst()
peek()
方法是从Queue
接口继承来的,它返回队列头部的元素,但不移除它。如果队列为空,则peek()
方法返回null
。
peekFirst()
方法是Deque
接口特有的,它与peek()
方法类似,也是返回队列头部的元素,但不移除它。然而,与peek()
方法不同的是,peekFirst()
方法不会抛出空指针异常,即使队列为空。当队列为空时,peekFirst()
方法返回null
。这使得peekFirst()
方法在某些情况下更加安全和可靠。
以此类推,其他的比如poll()和pollFirst()也是如此;
算法题
Leetcode 102.二叉树的层序遍历
题目链接:102.二叉树的层序遍历
大佬视频讲解:二叉树的层序遍历视频讲解
个人思路
层序遍历也就是图论中的广度优先遍历,是使用队列的结构;用队列一层层遍历,并加入结果数组,最后返回;
解法
迭代法
class Solution {public List<List<Integer>> resList = new ArrayList<List<Integer>>();public List<List<Integer>> levelOrder(TreeNode root) {checkFun01(root);return resList;}public void checkFun01(TreeNode root){if(root==null) return;Queue<TreeNode> que=new LinkedList<TreeNode>();que.offer(root);//加入节点while(!que.isEmpty()){//终止条件int len =que.size();//当前层数 元素的数量List<Integer> itemList=new ArrayList<Integer>();while(len>0){TreeNode temp=que.poll();itemList.add(temp.val);//结果子列表加入值if(temp.left!=null) que.offer(temp.left);//加入左节点if(temp.right!=null) que.offer(temp.right);len--;//遍历下一个节点}resList.add(itemList);//结果列表加入子列表}}}
时间复杂度:O(n);(遍历整棵树)
空间复杂度:O(n);(使用一个结果子列表,和一个列表)
递归法
class Solution {//1.确定递归函数的参数和返回值public List<List<Integer>> resList = new ArrayList<List<Integer>>();public List<List<Integer>> levelOrder(TreeNode root) {checkFun02(root,0);return resList;}public void checkFun02(TreeNode root, int deep){if(root==null) return;//2.确定递归终止条件//3.确定单层递归的逻辑deep++;//层级if(resList.size()<deep){//利用list的索引值进行层级界定List<Integer> item=new ArrayList<Integer>();resList.add(item); //当层级增加时,list的Item也增加}resList.get(deep-1).add(root.val);//get 层级 checkFun02(root.left,deep);//递归左子树checkFun02(root.right,deep);//递归右子树}
}
时间复杂度:O(n);(遍历整棵树)
空间复杂度:O(n);(使用一个结果子列表,和一个列表)
Leetcode 226.翻转二叉树
题目链接:226.翻转二叉树
大佬视频讲解:翻转二叉树视频讲解
个人思路
遍历二叉树,将每个节点的左右孩子交换一下
解法
这道题关键在于遍历顺序,这道题目使用前序遍历,后序遍历和层序遍历都可以,唯独中序遍历不方便,因为中序遍历会把某些节点的左右孩子翻转了两次
递归法(DFS)
class Solution {public TreeNode invertTree(TreeNode root) {//1.确定递归函数的参数和返回值if (root == null) {//2.确定终止条件return null;}//3.确定单层递归的逻辑invertTree(root.left);//后序遍历:左右中(根)invertTree(root.right);swapChildren(root);return root;}private void swapChildren(TreeNode root) {TreeNode tmp = root.left;root.left = root.right;root.right = tmp;}
}
时间复杂度:O(n);(遍历整棵树)
空间复杂度:O(n);(递归树的高度h,使用临时节点来换位)
迭代法(BFS)
class Solution {public TreeNode invertTree(TreeNode root) {if (root == null) {return null;}ArrayDeque<TreeNode> deque = new ArrayDeque<>();//队列进行层序遍历deque.offer(root);while (!deque.isEmpty()) {int size = deque.size();while (size-- > 0) {//遍历各层的各个节点TreeNode node = deque.poll();swap(node);//交换节点的左右子树if (node.left != null) deque.offer(node.left);if (node.right != null) deque.offer(node.right);}}return root;}public void swap(TreeNode root) {TreeNode temp = root.left;root.left = root.right;root.right = temp;}
}
时间复杂度:O(n);(遍历整棵树)
空间复杂度:O(n);(使用队列进行层序遍历和临时节点来换位)
Leetcode 101. 对称二叉树
题目链接:101. 对称二叉树
大佬视频讲解:对称二叉树视频讲解
个人思路
将二叉树分成两半一边为左子树A,一边为右子树B,然后就对比 A节点的左边和B节点的右边,A节点的右边和B节点的左边,都相等就对称了
解法
递归法
因为要遍历两棵树而且要比较内侧和外侧节点,所以一个树的遍历顺序是左右中,一个树的遍历顺序是右左中。
递归三部曲
1.确定递归函数的参数和返回值
因为要比较的是根节点的两个子树是否是相互翻转的,参数就是左子树节点和右子树节点。返回值为布尔类型。
2.确定终止条件
要比较两个节点数值相不相同,要把两个节点为空的情况分清楚,不然后面比较数值的时候就会操作空指针了。
节点为空的情况有:
- 1.左节点为空,右节点不为空,不对称,return false
- 左不为空,右为空,不对称 return false
- 左右都为空,对称,返回true
此时已经排除掉了节点为空的情况,那么剩下的就是左右节点不为空:左右都不为空,比较节点数值,不相同就return false
把以上情况都排除之后,剩下的就是 左右节点都不为空,且数值相同的情况。
3.确定单层递归的逻辑
处理 左右节点都不为空,且数值相同的情况。
1.比较二叉树外侧是否对称:传入的是左节点的左孩子,右节点的右孩子。
2.比较内侧是否对称,传入左节点的右孩子,右节点的左孩子。
如果左右都对称就返回true ,有一侧不对称就返回false 。
public boolean isSymmetric1(TreeNode root) {return compare(root.left, root.right);}private boolean compare(TreeNode left, TreeNode right) {1.确定递归函数的参数和返回值2.确定终止条件//处理节点为空的4种情况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;}3.确定单层递归的逻辑// 递归比较二叉树外侧boolean compareOutside = compare(left.left, right.right);// 递归比较内侧boolean compareInside = compare(left.right, right.left);return compareOutside && compareInside;//都为真则为真}
时间复杂度:O(n);(遍历二叉树)
空间复杂度:O(n);(递归树的高度h)
迭代法
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 || leftNode.val != rightNode.val) {return false;}//左子树的左节点和右子树的右节点deque.offer(leftNode.left);deque.offer(rightNode.right);//左子树的右节点和右子树的左节点deque.offer(leftNode.right);deque.offer(rightNode.left);}return true;}
时间复杂度:O(n);(遍历二叉树)
空间复杂度:O(n);(使用队列)
以上是个人的思考反思与总结,若只想根据系列题刷,参考卡哥的网址代码随想录算法官网