一文通数据结构与算法之——二叉树+常见题型与解题策略+Leetcode经典题
Posted 尚墨1111
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一文通数据结构与算法之——二叉树+常见题型与解题策略+Leetcode经典题相关的知识,希望对你有一定的参考价值。
文章目录
- 二叉树
- 1 二叉树基本操作
- 2 剑指 Offer 算法题
- 2.1 题目列表
- 2.2 实战
- [剑指 Offer 27. 二叉树的镜像](https://leetcode-cn.com/problems/er-cha-shu-de-jing-xiang-lcof/)
- [剑指 Offer 28. 对称的二叉树](https://leetcode-cn.com/problems/dui-cheng-de-er-cha-shu-lcof/)
- [剑指 Offer 26. 树的子结构](https://leetcode-cn.com/problems/shu-de-zi-jie-gou-lcof/)
- 其他类似的递归操作
- [剑指 Offer 55 - I. 二叉树的深度](https://leetcode-cn.com/problems/er-cha-shu-de-shen-du-lcof/)
- [剑指 Offer 55 - II. 平衡二叉树](https://leetcode-cn.com/problems/ping-heng-er-cha-shu-lcof/)
- [剑指 Offer 32 - I. 从上到下打印二叉树](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-lcof/)
- [剑指 Offer 32 - II. 从上到下打印二叉树 II](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-ii-lcof/)
- [剑指 Offer 32 - III. 从上到下打印二叉树 III](https://leetcode-cn.com/problems/cong-shang-dao-xia-da-yin-er-cha-shu-iii-lcof/)
- [剑指 Offer 07. 重建二叉树](https://leetcode-cn.com/problems/zhong-jian-er-cha-shu-lcof/)
- [剑指 Offer 37. 序列化二叉树](https://leetcode-cn.com/problems/xu-lie-hua-er-cha-shu-lcof/)
- [剑指Offer 36:二叉搜索树与双向链表](https://cuijiahua.com/blog/2017/12/basis_26.html)
- [剑指Offer 54:二叉搜索树的第k个结点](https://cuijiahua.com/blog/2018/01/basis_62.html)
- [剑指 Offer 68 - I. 二叉搜索树的最近公共祖先](https://leetcode-cn.com/problems/er-cha-sou-suo-shu-de-zui-jin-gong-gong-zu-xian-lcof/)
- [剑指 Offer 68 - II. 二叉树的最近公共祖先](https://leetcode-cn.com/problems/er-cha-shu-de-zui-jin-gong-gong-zu-xian-lcof/)(注意此时的返回扩展到了二叉树)
- [剑指 Offer 34. 二叉树中和为某一值的路径](https://leetcode-cn.com/problems/er-cha-shu-zhong-he-wei-mou-yi-zhi-de-lu-jing-lcof/)————回溯思想
- 3 其他
二叉树
1 二叉树基本操作
1.1 二叉树定义
public class TreeNode {
int val;
TreeNode left;
TreeNode right;
TreeNode(int x) { val = x; };
}
1.2 前、中、后序遍历
前中后的区别是,父节点输出的位置顺序
前:父左右
中:左父右
后:左右父
力扣题库:二叉树深度优先遍历
- 0144.二叉树的前序遍历
- 0145.二叉树的后序遍历
- 0094.二叉树的中序遍历
1.2.1 递归形式
//将二叉树以前序遍历的形式存在列表里,中序后序遍历代码一致,只是add的位置变换
ArrayList<Integer> list = new ArrayList<>();
public List<Integer> preorderTraversal(TreeNode root) {
if(root==null){
return list;
}else{
list.add(root.val);
if(root.left!=null){
preorderTraversal(root.left);
}
if(root.right!=null){
preorderTraversal(root.right);
}
}
return list;
}
1.2.2 非递归,迭代形式
迭代法前序遍历,要保持遍历的深度,所以需要用到一个stack来维持
//注意栈是先进后出,所以需要先把右子节点入栈
public static List<Integer> preOrder2(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode2> stack = new Stack<>();
if(root == null){
return list;
}
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.pop();
list.add(node.val);
if(node.right!=null){
stack.push(node.right);
}
if(node.left!= null){
stack.push(node.left);
}
}
return list;
}
迭代法后续遍历,同前序遍历,只需要把顺序反转一下
public List<Integer> postorderTraversal(TreeNode root) {
List<Integer> list = new ArrayList<>();
Stack<TreeNode2> stack = new Stack<>();
if(root == null){
return list;
}
stack.push(root);
while(!stack.isEmpty()){
TreeNode node = stack.pop();
list.add(node.val);
//注意这里左右子树的新增顺序也发生了变化
if(node.left!= null){
stack.push(node.left);
}
if(node.right!=null){
stack.push(node.right);
}
}
//这句反转的代码参考自leetCode官方,只是反转的操作会使效率降低
Collections.reverse(list);
return list;
}
中序遍历,分析
- 1.我们必须实现一直深度的入左子节点
- 2.中间节点在我们入左子节点的时候就相当于已经入栈
- 3.从左子节点(中间节点)如何跳到右子节点,所以这里需要增加一个
临时节点p
,用于保存当前的节点
public List<Integer> inorderTraversal(TreeNode2 root) {
List<Integer> res = new ArrayList<>();
Stack<TreeNode2> stack = new Stack<>();
//用临时节点p来保存中间节点信息
TreeNode2 p = root;
while(!stack.isEmpty() || p!=null){
//当前节点如果不为空,那么一直入栈,并且指向自己的左节点,实现中间节点和左子节点的入栈操作
while(p != null){
stack.push(p);
p = p.left;
}
//左子节点入栈完毕,开始出站。第一个出栈的就是最底层的那一个左子节点,入list
p = stack.pop();
res.add(p.val);
/**
1. 如果当前节点没有右子节点(叶子结点就没有),那么此时p=null
2. 如果当前节点不是叶子节点,实现了对右子节点的跳转输出
3. 所以此时的循环条件是,p非空说明没有遍历完,栈为空则说明出栈完毕,可以结束遍历
*/
p = p.right;
}
return res;
}
1.3 遍历方式
一来先判空
if(root==null){
return null;
}
遍历树的两种方式:递归和辅助栈(队列)
递归:典型应用
|-- 判断对称二叉树、相同二叉树、翻转二叉树、二叉树子树、合并二叉树、平衡二叉树
2.相同二叉树(同理)
public boolean isSameTree(TreeNode p, TreeNode q) {
//双指针,判断
if(p==null && q==null){
return true;
}
if(p == null || q == null){
return false;
}
return (p.val == q.val) && isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}
4.合并二叉树
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
if(t1==null || t2 ==null){
return t1==null?t2:t1;
}else{
t1.val = t1.val+t2.val;
t1.left = mergeTrees(t1.left,t2.left);
t1.right = mergeTrees(t1.right,t2.right);
return t1;
}
}
辅助栈(队列)
//辅助栈
public void test(){
if(root==null){
return null;
}
LinkedList<TreeNode> queue = new LinkedList<>();
queue.push(root);
while(!queue.isEmpty()){
TreeNode node = queue.poll();
if(node.left!=null){
queue.push(node.left);
}
if(node.right!=null){
queue.push(node.right);
}
}
}
2 剑指 Offer 算法题
2.1 题目列表
递归
遍历序列化和反序列化
剑指 Offer 32 - I. 从上到下打印二叉树【广度优先搜索BFS——队列】
剑指 Offer 32 - II. 从上到下打印二叉树 II
剑指 Offer 32 - III. 从上到下打印二叉树III
二叉搜索树
2.2 实战
剑指 Offer 27. 二叉树的镜像
//将所有左右节点进行互换
//递归
public TreeNode mirrorTree(TreeNode root) {
if(root==null){
return null;
}
TreeNode left = mirrorTree(root.left);
TreeNode right = mirrorTree(root.r);
root.left = right;
root.right = left;
return root;
}
//栈
public TreeNode mirrorTree2(TreeNode root) {
if(root==null){
return null;
}
LinkedList<TreeNode> queue = new LinkedList<>();
queue.push(root);
while(!queue.isEmpty()){
TreeNode node = queue.poll();
TreeNode temp = node.left;
node.left = node.right;
node.right = temp;
if(node.left!=null){
queue.push(node.left);
}
if(node.right!=null){
queue.push(node.right);
}
}
return root;
}
剑指 Offer 28. 对称的二叉树
//用来判断一棵二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
//递归法
public boolean isSymmetric(TreeNode root) {
return root==null || symmetric(root.left,root.right);
}
public boolean symmetric(TreeNode p,TreeNode q){
if(p==null && q==null) return true;
if(p==null || q==null || p.val!=q.val) return false;
return symmetric(p.left,q.right) && symmetric(p.right,q.left);
}
//辅助栈
public boolean symmetric(TreeNode p,TreeNode q) {
LinkedList<TreeNode> queue = new LinkedList<>();
queue.offer(p);
queue.offer(q);
while(!queue.isEmpty()){
TreeNode leftNode = queue.poll();
TreeNode rightNode = queue.poll();
// 1.入队列时就已经维持了对照位置,所以可以直接判断相等关系
if(leftNode==null && rightNode==null) continue;
if(leftNode==null || rightNode==null ||leftNode.val!=rightNode.val){
return false;
}
// 2.入队列
queue.offer(leftNode.left);
queue.offer(rightNode.right);
queue.offer(leftNode.right);
queue.offer(rightNode.left);
}
return true;
}
剑指 Offer 26. 树的子结构
//输入两棵二叉树A和B,判断B是不是A的子结构。(约定空树不是任意一个树的子结构)
//1.递归
public boolean isSubStructure(TreeNode A, TreeNode B) {
if(A==null || B==null) return false;
//这一步很有讲究 这样其实只是判断了三种情况,并没有递归下去。注意递归的精髓
//return isSame(A,B) || isSame(A.left,B) || isSame(A.right,B);
//isSame()只是一步判断是否相等的操作
return isSame(A,B) || isSame(A.left,B) || isSame(A.right,B);
}
public boolean isSame(TreeNode p,TreeNode q){
// 2.分情况讨论
// 当B为空,说明已经遍历完了,return true
// 当A为空说明越过了A的叶子节点还没有找到,return false
if(q==null) return true;
if(p==null|| p.val!=q.val) return false;
return isSame(p.left,q.left) && isSame(p.right,q.right);
}
其他类似的递归操作
2.相同二叉树(同理)
public boolean isSameTree(TreeNode p, TreeNode q) {
//双指针,判断
if(p==null && q==null){
return true;
}
if(p == null || q == null){
return false;
}
return (p.val == q.val) && isSameTree(p.left,q.left) && isSameTree(p.right,q.right);
}
4.合并二叉树
public TreeNode mergeTrees(TreeNode t1, TreeNode t2) {
if(t1==null || t2 ==null){
return t1==null?t2:t1;
}else{
t1.val = t1.val+t2.val;
t1.left = mergeTrees(t1.left,t2.left);
t1.right = mergeTrees(t1.right,t2.right);
return t1;
}
}
5.平衡二叉树
理解:左旋:右子树太长了,把它匀到左边。右旋同理。
1.先将根节点设置为新节点,将原左子树还是设为新的左子树
2.再把右子树的左子树设为新的右子树
3.把新节点作为右子树的 left,右子节点作为新的根节点
双旋转:并不是子树直接左旋右旋就能实现平衡
左旋时,右子节点的左子树高度 > 右子节点的右子树高度,要先对子树进行右旋
判断是否为平衡二叉树
public boolean isBalanced2(TreeNode root) {
if(root==null){
return true;
}else{
int leftHeight = depth(root.left);
int rightHeight = depth(root.right);
if(leftHeight-rightHeight>1 一文通数据结构与算法之——数组+常见题型与解题策略+Leetcode经典题
一文通数据结构与算法之——链表+常见题型与解题策略+Leetcode经典题
一文通数据结构与算法之——回溯算法+常见题型与解题策略+Leetcode经典题