《剑指offer》树专题 (牛客10.25)

考察的知识点主要在于树的数据结构(BST,AVL)、遍历方式(前序,中序,后序,层次)、遍历算法(DFS,BFS,回溯)以及遍历时借助的数据结构如队列和栈。由于树本身就是一个递归定义的结构,所以在递归求解问题时,要善于将问题转化成合适的子问题,思考清楚子问题的形式、递归的出口、父问题与子问题的联系。

以下这些问题难度都不太大,很多都是一次过,比上次链表专题的思维难度小多了。

难度 题目 知识点
04. 重建二叉树 依据前序和中序遍历重建 递归
★★ 17. 树的子结构 递归
18. 二叉树的镜像 简单递归
22. 从上往下打印二叉树 bfs Queue的使用
23. 判断是否为二叉搜索树的后序遍历 BST 递归
24. 二叉树中和为某一值的路径 dfs 回溯 Collections.sort()
★★ 26. 二叉搜索树与双向链表 递归 中序遍历
38. 二叉树的深度 递归
39. 平衡二叉树 平衡二叉树 递归
57. 二叉树的下一个结点 中序遍历 循环 回溯后的情况
58. 对称的二叉树 递归 可转化为非递归
59. 按之字形顺序打印二叉树 Stack 层次遍历变形
60. 把二叉树打印成多行 Queue LinkedList 层次遍历
★★ 61. 序列化二叉树 递归 string.split() string.equals
62. 二叉搜索树的第K个结点 BST 中序遍历 递归

04. 重建二叉树

依据前序和中序遍历重建 递归

输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列{1,2,4,7,3,5,6,8}和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。

递归建树。注意递归出口,当有 0 或一个节点时直接返回。

/**
 * Definition for binary tree
 * struct TreeNode {
 *     int val;
 *     TreeNode *left;
 *     TreeNode *right;
 *     TreeNode(int x) : val(x), left(NULL), right(NULL) {}
 * };
 */
class Solution {
public:
    TreeNode* reConstructBinaryTree(vector<int> pre,vector<int> vin) {
        if(pre.size()==0)
            return NULL;
        if(pre.size()==1)
            return new TreeNode(pre[0]);
        TreeNode* root = new TreeNode(pre[0]);
        int lenl =0, lenr = 0,len = vin.size();
        while(lenl<len && vin[lenl]!= pre[0])
            lenl++;
        vector<int> npre,nvin;
        
        npre = vector<int>(pre.begin()+1,pre.begin()+1+lenl);
        nvin = vector<int>(vin.begin(),vin.begin()+lenl);
        root->left = reConstructBinaryTree(npre,nvin);
        
        npre = vector<int>(pre.begin()+1+lenl,pre.end());
        nvin = vector<int>(vin.begin()+lenl+1,vin.end());
        root->right = reConstructBinaryTree(npre,nvin);
        
        return root;
    }
};

17. 树的子结构 ++

递归

输入两棵二叉树A,B,判断B是不是A的子结构。(ps:我们约定空树不是任意一个树的子结构)

/**
A:  两个步骤:1.【包不包括】判断顶点值是否相同
            2.【是不是】顶点值的情况下A与B能不能对应上(注意A可能是大于B的
*/
public class Solution {
    public boolean HasSubtree(TreeNode root1,TreeNode root2) {
        if(root2 == null || root1 == null)
            return false;
        
        if(root1.val == root2.val && IsSubtree(root1,root2))
            return true;
        else
            return HasSubtree(root1.left,root2) 
            || HasSubtree(root1.right,root2);
    }
    
    private boolean IsSubtree(TreeNode root1,TreeNode root2){
        if(root2 == null)return true;
        if(root1 == null)return false;
        
        if(root1.val != root2.val) return false;
        
        return IsSubtree(root1.left,root2.left) 
            && IsSubtree(root1.right,root2.right);
    }
}

18. 二叉树的镜像

简单递归

操作给定的二叉树,将其变换为源二叉树的镜像。

二叉树的镜像定义:源二叉树 
          8
         /  \
        6   10
       / \  / \
      5  7 9 11
      镜像二叉树
          8
         /  \
        10   6
       / \  / \
      11 9 7  5
public class Solution {
    public void Mirror(TreeNode root) {
        if(root == null) return;
        
        TreeNode tmp = root.left;
        root.left = root.right;
        root.right = tmp;
        
        Mirror(root.left);
        Mirror(root.right);
    }
}

22. 从上往下打印二叉树

bfs Queue的使用

Queue的使用:

Java 集合中的 Queue 继承自 Collection 接口 ,Deque, LinkedList, PriorityQueue, BlockingQueue 等类都实现了它。

offer,add 比较:
共同之处是建议实现类禁止添加 null 元素,否则会报空指针 NullPointerException;
不同之处在于 add() 方法在添加失败(比如队列已满)时会报一些运行时错误;而 offer() 方法即使在添加失败时也不会崩溃,只会返回 false。

poll,remove 比较:
remove() 和 poll() 方法都是从队列中删除第一个元素。remove() 的行为与 Collection 接口的版本相似,但是新的 poll() 方法在用空集合调用时不是抛出异常,只是返回 null。因此新的方法更适合容易出现异常条件的情况。

peek,element 比较:
element() 和 peek() 用于在队列的头部查询元素。与 remove() 方法类似,在队列为空时, element() 抛出一个异常,而 peek() 返回 null。

Java Code

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
public class Solution {
    public ArrayList PrintFromTopToBottom(TreeNode root) {
        ArrayList ans = new ArrayList<>();
        Queue queue = new LinkedList();
        if (root == null) return ans;
        queue.add(root);
        while (!queue.isEmpty()) {
            TreeNode poll = queue.poll();
            ans.add(poll.val);
            if (poll.left != null)
                queue.offer(poll.left);
            if (poll.right != null)
                queue.offer(poll.right);
        }
        return ans;
    }
}
    

23. 判断是否为二叉搜索树的后序遍历

BST 递归

题目描述

输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历的结果。如果是则输出Yes,否则输出No。假设输入的数组的任意两个数字都互不相同。

递归思路:
已知条件:后序序列最后一个值为root;二叉搜索树左子树值都比root小,右子树值都比root大。

  1. 确定root;
  2. 遍历序列(除去root结点),找到第一个大于root的位置,则该位置左边为左子树,右边为右子树;
  3. 遍历右子树,若发现有小于root的值,则直接返回false;
  4. 分别判断左子树和右子树是否仍是二叉搜索树(即递归步骤1、2、3)。
Java Code

public class Solution {
    public boolean VerifySquenceOfBST(int[] sequence) {
        if (sequence == null || sequence.length == 0)
            return false;
        return check(sequence, 0, sequence.length - 1);
    }
    // [ , ]
    private boolean check(int[] tree, int start, int end) {
        if (start >= end)
            return true;
        int root = tree[end];
        int mid = start;
        while (mid < end && tree[mid] < root) mid++;
        int p = mid;
        while (p < end) {
            if (tree[p] < root)
                return false;
            p++;
        }
        return check(tree, start, mid - 1) && check(tree, mid, end - 1);
    }
}
    

24. 二叉树中和为某一值的路径+

dfs 回溯 Collections.sort()

题目描述

输入一颗二叉树的根节点和一个整数,打印出二叉树中结点值的和为输入整数的所有路径。路径定义为从树的根结点开始往下一直到叶结点所经过的结点形成一条路径。(注意: 在返回值的list中,数组长度大的数组靠前)

题目思路

深搜,搜到叶子时若和为target便将路径加入路径列表。路径列表排序后即为答案。

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;

public class Solution {
    ArrayList<Integer> path = new ArrayList<>();
    ArrayList<ArrayList<Integer>> pathList = new ArrayList<>();

    public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
        dfs(root, target);
        Collections.sort(pathList, new Comparator<ArrayList<Integer>>() {
            @Override
            public int compare(ArrayList<Integer> o1, ArrayList<Integer> o2) {
                if (o1.size() >= o2.size())
                    return -1;
                else
                    return 1;
            }
        });
        return pathList;
    }

    public void dfs(TreeNode root, int target) {
        if (root == null) return;
        path.add(root.val);

        if (target == root.val && root.left == null && root.right == null)
            pathList.add((ArrayList<Integer>) path.clone());
        else {
            dfs(root.left, target - root.val);
            dfs(root.right, target - root.val);
        }

        path.remove(path.size() - 1);

    }

}

26. 二叉搜索树与双向链表++

递归 中序遍历

题目描述

输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。

解题思路

方法 1:先拍平左子树,再拍平右子树。左、中、右连接。【递归实现】

方法 2:【更简化】中序遍历,用一个 pLast 记录总的链表的末尾 ☆

Java代码

// 方法 1 代码
// 不能用 javax 的 Pair 所以写了个类中类
// java 的统一引用真是好哇
public class Solution {
    public TreeNode Convert(TreeNode pRootOfTree) {
        if(pRootOfTree==null)
            return pRootOfTree;

        return getFlat(pRootOfTree).min;
    }

    public Pair getFlat (TreeNode root){
        if(root.left==null && root.right==null)
            return new Pair(root,root);

        TreeNode left,right,minn=root,maxx=root;
        Pair tmp;
        if(root.left!=null) {
            tmp = getFlat(root.left);
            minn = tmp.min;
            left = tmp.max;
            left.right=root;
            root.left=left;
        }
        if(root.right!=null){
            tmp=getFlat(root.right);
            right = tmp.min;
            maxx = tmp.max;
            right.left=root;
            root.right=right;
        }
        return new Pair(minn,maxx);
    }
    private class Pair{
        TreeNode min,max;
        Pair(TreeNode a,TreeNode b){
            this.min=a;
            this.max=b;
        }
    }
}
// ---------------------方法 2-----------------------------
private TreeNode pLast = null;
public TreeNode Convert(TreeNode root) {
 if (root == null)
     return null;
 
 // 如果左子树为空,那么根节点root为双向链表的头节点
 TreeNode head = Convert(root.left);
 if (head == null)
     head = root;
 
 // 连接当前节点root和当前链表的尾节点pLast
 root.left = pLast;
 if (pLast != null)
     pLast.right = root;
 pLast = root;
 
 Convert(root.right);
 
 return head;
}

38. 二叉树的深度+

递归

题目描述

输入一棵二叉树,求该树的深度。从根结点到叶结点依次经过的结点(含根、叶结点)形成树的一条路径,最长路径的长度为树的深度。

相当简洁的写法!

public class Solution {
    public int TreeDepth(TreeNode root) {
        if(root == null) return 0;
        return Math.max(TreeDepth(root.left)+1,TreeDepth(root.right)+1);
    }
}

39. 平衡二叉树

平衡二叉树 递归

题目描述

输入一棵二叉树,判断该二叉树是否是平衡二叉树。

平衡二叉树

平衡二叉树(Balanced Binary Tree)具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。平衡二叉树的常用实现方法有红黑树AVL替罪羊树Treap伸展树等。 最小二叉平衡树的节点的公式如下 F(n)=F(n-1)+F(n-2)+1 这个类似于一个递归的数列,可以参考Fibonacci数列,1是根节点,F(n-1)是左子树的节点数量,F(n-2)是右子树的节点数量。——百度百科

Java Code

public class Solution {
    public boolean IsBalanced_Solution(TreeNode root) {
        if(Balance_depth(root)==-1)
            return false;
        else   
            return true;
    }

    private int Balance_depth(TreeNode root){
        if(root == null )
            return 0;

        int dLeft=Balance_depth(root.left);
        if(dLeft==-1) return -1;
        int dRight=Balance_depth(root.right);

        if(dRight == -1||Math.abs(dLeft-dRight)>1)
            return -1;
        return Math.max(dLeft,dRight)+1;
    }
}

57. 二叉树中序遍历的下一个结点+

中序遍历 循环 回溯后的情况

题目描述

给定一个二叉树和其中的一个结点,请找出中序遍历顺序的下一个结点并且返回。注意,树中的结点不仅包含左右子结点,同时包含指向父结点的指针。

解题思路
对于一个节点,

  • 如果有右子树,返回右子树最左下的节点;
  • 如果没有右子树,从此节点开始,返回第一个作为左儿子的节点的父亲,没有符合条件的就返回null。

Java Code

public class Solution {
    public TreeLinkNode GetNext(TreeLinkNode pNode) {
        TreeLinkNode ans = null;
        if (pNode == null) return ans;

        TreeLinkNode p = pNode;
        if (p.right != null) {// 有右子树
            ans = p.right;
            while (ans.left != null)
                ans = ans.left;
        } else {
            ans = p;
            while (ans != null && ans.next != null && ans == ans.next.right)
                ans = ans.next;
            if (ans != null) {
                    ans = ans.next;
            }

        }
        return ans;
    }
}

58. 对称的二叉树

递归 可转化为非递归

题目描述

请实现一个函数,用来判断一颗二叉树是不是对称的。注意,如果一个二叉树同此二叉树的镜像是同样的,定义其为对称的。

public class Solution {
    boolean isSymmetrical(TreeNode pRoot) {
        if(pRoot == null) return true;
        return check(pRoot.left, pRoot.right);
    }

    boolean check(TreeNode rt1, TreeNode rt2) {
        if (rt1 == null && rt2 == null) return true;
        else if (rt1 == null || rt2 == null) {
            return false;
        } else {
            return rt1.val == rt2.val &&
                    check(rt1.left, rt2.right) && check(rt1.right, rt2.left);
        }
    }
}

59. 按之字形顺序打印二叉树

Stack 层次遍历变形

题目描述

请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。

用两个栈交互倒,元素在两个栈的左右儿子压站顺序稍有不同。

Stack 的 peek() 只取第一个元素,删除用 pop()。

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;

public class Solution {
    public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
        ArrayList<ArrayList<Integer>> ans = new ArrayList<>();
        if(pRoot==null) return ans;

        Stack<TreeNode> s1=new Stack<>();
        Stack<TreeNode> s2=new Stack<>();

        s1.push(pRoot);
        ArrayList<Integer> curList;
        TreeNode cur;
        while(!s1.empty()||!s2.empty()){
            curList = new ArrayList<>();
            while (!s1.empty()){
                cur = s1.peek();
                s1.pop();
                curList.add(cur.val);
                if(cur.left!=null)s2.push(cur.left);
                if(cur.right!=null)s2.push(cur.right);
            }
            ans.add(curList);
            curList = new ArrayList<>();
            while(!s2.empty()){
                cur = s2.peek();
                s2.pop();
                curList.add(cur.val);
                if(cur.right!=null) s1.push(cur.right);
                if(cur.left!=null)s1.push(cur.left);
            }
            if(curList.size()>0)    ans.add(curList);
        }

        return ans;

    }

}

60. 把二叉树打印成多行

Queue LinkedList 层次遍历

题目描述

从上到下按层打印二叉树,同一层结点从左至右输出。每一层输出一行。

思路和上题相似,轮流使用两个队列避免用变量区别层数。

Java Code

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.Queue;
import java.util.Stack;
public class Solution {
    public ArrayList> Print(TreeNode pRoot) {
        ArrayList> ans = new ArrayList<>();
        if(pRoot==null) return ans;
        Queueq1=new LinkedList<>();
        Queueq2=new LinkedList<>();
        TreeNode cur;
        ArrayList curList;
        q1.offer(pRoot);
        while (!q1.isEmpty()){
            curList = new ArrayList<>();
            while(!q1.isEmpty()){
                cur = q1.peek();
                q1.poll();
                curList.add(cur.val);
                if (cur.left!=null)q2.offer(cur.left);
                if(cur.right!=null)q2.offer(cur.right);
            }
            ans.add(curList);
            curList = new ArrayList<>();
            while (!q2.isEmpty()){
                cur=q2.peek();
                q2.poll();
                curList.add(cur.val);
                if (cur.left!=null)q1.offer(cur.left);
                if(cur.right!=null)q1.offer(cur.right);
            }
            if(curList.size()>0)ans.add(curList);
        }
        return ans;
    }
}
    

61. 序列化二叉树++

递归 str.split() str.equals

题目描述

请实现两个函数,分别用来序列化和反序列化二叉树

二叉树的序列化:把一棵二叉树按照某种遍历方式的结果以某种格式保存为字符串,从而使得内存中建立起来的二叉树可以持久保存。序列化可以基于先序、中序、后序、层序的二叉树遍历方式来进行修改,序列化的结果是一个字符串,序列化时通过 某种符号表示空节点(#),以 ! 表示一个结点值的结束(value!)。

二叉树的反序列化:根据某种遍历顺序得到的序列化字符串结果str,重构二叉树。

递归实现。其中,反序列化需要借助一个变量记录反序列化的进展。

注意字符串的比较要用equals()。

import java.io.Serializable;
import java.util.ArrayList;

public class Solution {
    String Serialize(TreeNode root) {
        if (root == null) return "";
        StringBuilder ans = new StringBuilder();
        Serialize2(root, ans);
        return ans.toString();
    }

    private void Serialize2(TreeNode rt, StringBuilder ans) {
        if (rt == null) ans.append("#!");
        else {
            ans.append(Integer.toString(rt.val));
            ans.append('!');
            Serialize2(rt.left, ans);
            Serialize2(rt.right, ans);
        }
    }

    private int idx=0;
    TreeNode Deserialize(String str) {
        if (str == null||str.equals("")) return null;

        TreeNode rt = null;
        String[] nodes;
        nodes = str.split("!");
        idx = 0;
        return Deserilize2(nodes);
    }

    private TreeNode Deserilize2(String[] nodes) {
        if (nodes[idx].equals("#")) {
            idx++;
            return null;
        }
        TreeNode rt = new TreeNode(new Integer(nodes[idx]));
        idx++;
        rt.left = Deserilize2(nodes);
        rt.right = Deserilize2(nodes);
        return rt;
    }
}

62. 二叉搜索树的第K个结点

BST 中序遍历 递归

题目描述

给定一棵二叉搜索树,请找出其中的第k小的结点。例如, (5,3,7,2,4,6,8) 中,按结点数值大小顺序第三小结点的值为4。

BST中序遍历的第K个就是第K小。

public class Solution {

    private int no=0;   // 记录已经中序遍历了几个元素。
    TreeNode KthNode(TreeNode pRoot, int k)
    {
        if(pRoot==null||k==0)   return null;

        TreeNode tmp=KthNode(pRoot.left,k);
        if(tmp!=null)    return tmp;    // 左子树搜寻结果 
        if((++no)==k)   return pRoot;   // 自己是不是
        return KthNode(pRoot.right,k);  // 右子树搜寻结果
    }

}
posted @ 2019-10-26 15:42  武藏小次郎  阅读(...)  评论(...编辑  收藏