《剑指offer》刷题(2)树

今天写写《剑指offer》中树的解题思路

07.重建二叉树

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。
思路:前序遍历[根左右],中序遍历[左根右],用hashmap存储中序遍历的引索,用递归来建立树

public TreeNode buildTree(int[] preorder, int[] inorder) {
       Map<Integer, Integer> map = new HashMap<>();
       for (int i = 0; i < inorder.length; i++) {
           map.put(inorder[i], i);
       }
       int len = preorder.length - 1;
       return rebuild(preorder, 0, len, 0, len, map);
   }

   public TreeNode rebuild(int[] preorder, int preL, int preR, int inL, int inR, Map<Integer, Integer> map) {
       if (preL > preR) {
           return null;
       }
       int rootVal = preorder[preL];
       int inRoot = map.get(rootVal);
       TreeNode root = new TreeNode(rootVal);
       root.left = rebuild(preorder, preL + 1, preL + inRoot - inL, inL, inRoot - 1, map);
       root.right = rebuild(preorder, preL + 1 + inRoot - inL, preR, inRoot + 1, inR, map);
       return root;
   }

26.树的子结构

很简单,递归,要么B是A树的子结构,或者是A的左子树的子结构,或者A的右子树的子结构

public boolean isSubStructure(TreeNode A, TreeNode B) {
       if(A==null||B==null) return false;
       return recur(A,B)||isSubStructure(A.left,B)||isSubStructure(A.right,B);
   }
   public boolean recur(TreeNode A, TreeNode B) {
       if(B==null){
           return true;
       }
       if(A==null||B==null){
           return false;
       }
       if(A.val!=B.val){
           return false;
       }
       return recur(A.left,B.left)&&recur(A.right,B.right);
   }

27.二叉树的镜像

递归写法

递归写法
public TreeNode mirrorTree(TreeNode root) {
    if(root == null) return null;
    TreeNode temp = root.left;
    root.left = mirrorTree(root.right);
    root.right = mirrorTree(temp); 
    return root;
}
BFS写法:加入队列前交换
public TreeNode mirrorTree(TreeNode root) {
    if(root == null) return root;
    Queue<TreeNode> queue=new LinkedList<>();
    queue.add(root);
    while(!queue.isEmpty()){
        int sz=queue.size();
        for(int i=0;i<sz;i++){
            TreeNode cur=queue.poll();
            TreeNode temp=cur.left;
            cur.left=cur.right;
            cur.right=temp;
            if(cur.left!=null){
                queue.offer(cur.left);
            }
            if(cur.right!=null){
                queue.offer(cur.right);
            }
        }
    }
    return root;
}

32.Z型从上到下打印二叉树

BFS加双向队列Deque就信了:奇数行removeFirst,addLast;偶数行removeLast,addFirst;用deque.isEmpty()来判断是否要继续BFS

public List<List<Integer>> levelOrder(TreeNode root) {
    Deque<TreeNode> deque = new LinkedList<>();
    List<List<Integer>> res = new ArrayList<>();
    if(root != null) deque.add(root);
    while(!deque.isEmpty()){
        //先是奇数行
        List<Integer> temp = new ArrayList<>();
        for(int i = deque.size(); i > 0;i--){
            TreeNode node = deque.removeFirst();
            temp.add(node.val);
            if(node.left != null) deque.addLast(node.left);
            if(node.right != null) deque.addLast(node.right);//从后面加
        }
        res.add(temp);
        if(deque.isEmpty()){
            break;
        }
        //偶数行
        temp = new ArrayList<>();
        for(int i = deque.size(); i > 0;i--){
            TreeNode node = deque.removeLast();
            temp.add(node.val);
            if(node.right != null) deque.addFirst(node.right);
            if(node.left != null) deque.addFirst(node.left);
            
        }
        res.add(temp);
    }
    return res;
}

34.二叉树中和为某一值的路径(重点)

法一:回溯

  • 回溯强调在状态空间大的时候,可以一份状态变量去搜索所有的可能的状态
  • 在搜索到符合条件的解的时候,通常会做一个拷贝,经常在递归终止条件的时候,有 res.add(new ArrayList<>(path));
  • 正是因为全程使用一份状态变量,因此它就有恢复现场(return)和撤销选择(remove最后一个节点值)的需要
public List<List<Integer>> pathSum(TreeNode root, int sum) {
    List<List<Integer>> res=new ArrayList<>();
    if(root==null) return res;
    List<Integer> path=new ArrayList<>();//一份变量
    dfs(root,sum,path,res);
    return res;
}
public void dfs(TreeNode root,int sum,List<Integer> path,List<List<Integer>> res){
    //叶子节点就是base case咯
    if(root.left==null&&root.right==null){
        if(root.val==sum){
            path.add(root.val);
            res.add(new ArrayList<Integer>(path));
            path.remove(path.size()-1);//撤销选择
        }
        return;//恢复现场
    }
    if(root.left==null){
        path.add(root.val);
        dfs(root.right,sum-root.val,path,res);
        path.remove(path.size()-1);
        return;
    }
    if(root.right==null){
        path.add(root.val);
        dfs(root.left,sum-root.val,path,res);
        path.remove(path.size()-1);
        return;
    }
    path.add(root.val);
    dfs(root.left,sum-root.val,path,res);
    path.remove(path.size()-1);
    path.add(root.val);
    dfs(root.right,sum-root.val,path,res);
    path.remove(path.size()-1);
}

法二:回溯

LinkedList<List<Integer>> res = new LinkedList<>();
LinkedList<Integer> path = new LinkedList<>();
public List<List<Integer>> pathSum(TreeNode root, int sum) {
    recur(root,sum);
    return res;
}
public void recur(TreeNode root,int tar){
    if(root==null) return;
    path.add(root.val);
    tar -= root.val;
    //先序遍历
    if(tar==0 && root.left==null && root.right==null){
        res.add(new LinkedList(path));//一定要new一个
    }
    recur(root.left,tar);
    recur(root.right, tar);
    path.removeLast();//记得回溯
}

37. 序列化二叉树

困难题,但是结合BFS就是处理一些细节上的问题了,主要是对null处理

  1. 因为要序列化null,所以BFS提交队列的时候不判断是否为null,在弹出队列的时候判断即可;
  2. 反序列化的时候,遇见null(说明poll的节点是叶子)什么也不做,更新引索
public String serialize(TreeNode root) {
    if(root==null) return "[]";
    StringBuilder res=new StringBuilder("[");
    Queue<TreeNode> queue=new LinkedList<>();
    queue.add(root);
    while (!queue.isEmpty()){
        int sz=queue.size();
        for(int i=0;i<sz;i++){
            TreeNode cur=queue.poll();
            if(cur!=null){
                res.append(cur.val+",");
                queue.offer(cur.left);
                queue.offer(cur.right);
            }else{
                res.append("null,");
            }
        }
    }
    res.deleteCharAt(res.length()-1);
    res.append("]");
    return res.toString();
}

public TreeNode deserialize(String data) {
    if(data.equals("[]")) return null;
    String[] vals=data.substring(1,data.length()-1).split(",");
    TreeNode root=new TreeNode(Integer.parseInt(vals[0]));
    Queue<TreeNode> queue=new LinkedList<>();
    queue.add(root);
    int i=1;
    while (!queue.isEmpty()){
        TreeNode cur=queue.poll();
        //vals[i]==null的话就不更新左右子节点
        if(!vals[i].equals("null")){
            TreeNode node=new TreeNode(Integer.parseInt(vals[i]));
            cur.left=node;
            queue.offer(node);
        }
        i++;
        if(!vals[i].equals("null")){
            TreeNode node=new TreeNode(Integer.parseInt(vals[i]));
            cur.right=node;
            queue.offer(node);
        }
        i++;
    }
    return root;
}

54. 二叉搜索树的第k大节点(经典)

第一次做完全不觉得是简单难度的,思路:全局变量+中序遍历倒序+剪枝

int res,k;
public int kthLargest(TreeNode root, int k) {
    this.k=k;
    dfs(root);
    return res;
}
void dfs(TreeNode root){
    if(root==null) return;
    dfs(root.right);//中序遍历倒序,先right后left
    if(k==0) return;//剪枝,在k=0后,无需遍历,提前返回
    if(--k==0) res=root.val;//当更新k为0的时候,得到res,在下一轮遍历中return
    dfs(root.left);
}

55 - II. 平衡二叉树(经典)

最佳解法:后序遍历(时间空间都是线性的)+剪枝(一旦某个节点的左右子树的高度相差大于1,立即返回)

public boolean isBalanced(TreeNode root) {
    if(dfs(root)!=-1) return true;
    return false;
}
private int dfs(TreeNode root){
    if(root==null) return 0;
    int left=dfs(root.left);
    if(left==-1) return -1;
    int right=dfs(root.right);
    if(right==-1) return -1;
    return Math.abs(left-right)<2?Math.max(left,right)+1:-1;
}

68 - I. 二叉搜索树的最近公共祖先

p,q的val都大于root的,在root的右子树
p,q的val都小于root的,在root的左子树
否则就是root

迭代法
public TreeNode lowestCommonAncestor_v2(TreeNode root, TreeNode p, TreeNode q) {
    while (root != null) {
        if (root.val < p.val && root.val < q.val) // p,q 都在 root 的右子树中
            root = root.right; // 遍历至右子节点
        else if (root.val > p.val && root.val > q.val) // p,q 都在 root 的左子树中
            root = root.left; // 遍历至左子节点
        else break;//否则就是root
    }
    return root;
}
递归法
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    if (root == null) return null;
    if (p.val < root.val && q.val < root.val) return lowestCommonAncestor(root.left, p, q);
    if (p.val > root.val && q.val > root.val) return lowestCommonAncestor(root.right, p, q);
    return root;
}

68 - II. 二叉树的最近公共祖先(重点)

假设设root是p和q的公共祖先,只有以下三种情况:

  • p和q在root的子树中,在root的不同侧
  • p=root,q在root的左右子树中
  • q=root,p在root的左右子树中
    考虑通过递归对二叉树进行后序遍历,当遇到节点 p 或 q 时返回。从底至顶回溯,当节点 p, q 在节点 root 的异侧时,节点 root 即为最近公共祖先,则向上返回 root 。
public TreeNode lowestCommonAncestor_2(TreeNode root, TreeNode p, TreeNode q) {
	//后序遍历
    if (root == null || root == p || root == q) return root;
    TreeNode left = lowestCommonAncestor(root.left, p, q);
    TreeNode right = lowestCommonAncestor(root.right, p, q);
    if (left == null) return right;
    if (right == null) return left;
    return root;//left right同时不为空,说明root是最近公共祖先
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值