1、采用前序遍历的方式,将一个二叉树转换成一个由括号和整数组成的字符串。空节点则用一对空括号 "()" 表示。省略所有不影响字符串与原始二叉树之间的一对一映射关系的空括号对。
示例:
思路:
如果根节点不为空的话,在字符串后边先打印一个左括号,然后再打印根节点的值。
如果左子树为空,而右子树不为空,像示例2的那样,就必须打印一对括号出来,不可以省略(如果左右子树都为空,可以省略括号)。
其余情况就递归遍历根节点的左子树,然后再递归遍历根节点的右子树,最后在字符串的后面加上右括号。
用上面的示例1具体来说就是:
- 先打印一个左括号,然后打印根节点1,判断它的左子树不为空,所以接下来要递归遍历1的左子树,至此,已打印的字符串为
(1
。 - 以2为根节点,先打印一个左括号,然后打印2,因为2的左子树也不为空,所以再递归遍历2的左子树,已打印的字符串为
(1(2
。 - 以4为根节点,先打印一个左括号,再打印4,由于4的左右子树都为空,直接在字符串后边加上右括号,已打印的字符串为
(1(2(4)
。 - 现在2的左子树已经遍历完了,接着遍历它的右子树,为空,所以打印一个右括号,已打印的字符串为
(1(2(4))
。 - 1的左子树也遍历完了,现在去遍历1的右子树,以3为根节点,先打印一个左括号,再打印3,然后去遍历3的左右子树,都为空,所以打印一个右括号,已打印的字符串为
(1(2(4))(3)
。 - 1的左右子树都遍历完了,在字符串最后加上一个右括号,整棵二叉树遍历完毕,打印出的字符串为
(1(2(4))(3))
。
但是打印出来的结果与题目不符,题目要求的是没有最外面这层括号的,很简单,我们可以在打印完所有字符串之后再删掉最外面这层括号就行。
具体代码如下:
private static void preOrderTree2Str(TreeNode root, StringBuilder sb) {
if (root != null) {
sb.append('(');
sb.append(root.val);
if (root.left == null && root.right != null) {
sb.append("()");
} else {
preOrderTree2Str(root.left, sb);
}
preOrderTree2Str(root.right, sb);
sb.append(')');
}
}
public static String tree2str(TreeNode root) {
if (root == null) {
return "";
}
StringBuilder sb = new StringBuilder();
preOrderTree2Str(root, sb);
sb.delete(0, 1);
sb.delete(sb.length() - 1, sb.length());
return sb.toString();
}
2、给定一个二叉树, 找到该树中两个指定节点的最近公共祖先。
示例:
思路:
首先判断要比较的两个节点是否有一个是这棵树的根节点,如果其中一个是根节点,那么最近的公共祖先就是根节点。
其次再去查找这两个节点到底在左子树还是右子树,如果都在左子树,就递归遍历左子树,查找最近公共祖先,如果都不在左子树,就去递归遍历右子树查找最近公共祖先,如果一个在左子树一个在右子树,那么最近公共祖先就是根节点。
直接看代码:
private boolean find(TreeNode root, TreeNode t) {
if (root == null) {
return false;
}
if (root == t) {
return true;
}
if (find(root.left, t)) {
return true;
}
return find(root.right, t);
}
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
if (root == p || root == q) {
return root;
}
boolean pInleft = find(root.left, p);
boolean qInleft = find(root.left, q);
if (pInleft && qInleft) {
return lowestCommonAncestor(root.left, p, q);
}
if (!pInleft && !qInleft) {
return lowestCommonAncestor(root.right, p, q);
}
return root;
}
3、如何实现层序遍历
思路:
首先创建一个队列,把根节点尾插到队列中。只要这个队列不为空,先出掉队列中的第一个节点,打印它的值,如果出掉的这个节点的左孩子不为空,就把左孩子尾插到队列中,如果右孩子也不为空,也尾插到队列中,一直循环,直到队列为空。
图解过程:
至此,队列为空,层序遍历结束。
代码如下:
public void levelOrder(TreeNode root) {
if (root == null) {
return;
}
LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
//启动
queue.addLast(root);
//拉下线整个过程
while (!queue.isEmpty()) {
TreeNode front = queue.pollFirst();
System.out.print(front);
//拉下线,有要求,空的不要
if (front.left != null) {
queue.addLast(front.left);
}
if (front.right != null) {
queue.addLast(front.right);
}
}
}
4、给定一个二叉树,返回其按层次遍历的节点值。 (即逐层地,从左到右访问所有节点)。
思路:
这是一道层序遍历的变型题,本质还是层序遍历。与层序遍历不同的是,这次不仅需要返回一个节点,还要返回节点所在的层数。也就是说,又要返回两个值,在Java中返回两个值需要定义一个类,把要返回的这两个值放到同一个类中,然后返回这个类。
具体操作是,新建一个队列,先把根节点尾插到队列中,层数为0,只要队列不为空,先出掉队列中的第一个元素,出掉的这个元素包含两部分内容,一个是节点,一个是节点所在的层数。
如果出掉的节点的左孩子不为空,就将它的左孩子入队列,当前层数加1,右孩子不为空的话也入队列,当前层数加1。
上面的是层序遍历的过程,接下来要处理返回值。如果根节点为空,直接返回空的list。如果当前list的大小等于层数,说明list里一个元素都没有,就像下面这样:
我们需要做的,就是在list里边新建一个arrayList,用来保存每一层的元素。
然后将当前这一层的节点的值放入arrayList中,也就是将第0层的A放入arrayList中。
现在list的大小为1,而B的层数也为1,所以新建一个arrayList,将第1层的B放入新的arrayList中。
list大小变为2,而C的层数为1,所以直接将C放入刚刚的arrayList中。
DEF与上述操作一样,最后返回list。直接看代码:
public List<List<Integer>> levelOrder2(TreeNode root) {
List<List<Integer>> list = new ArrayList<>();
if (root == null) {
return list;
}
class NodeLevel {
TreeNode node;
int level;
public NodeLevel(TreeNode node, int level) {
this.node = node;
this.level = level;
}
}
LinkedList<NodeLevel> queue = new LinkedList<NodeLevel>();
queue.addLast(new NodeLevel(root, 0));
while (!queue.isEmpty()) {
NodeLevel front = queue.pollFirst();
TreeNode node = front.node;
int level = front.level;
//中间的遍历
if (list.size() == level) {
list.add(new ArrayList<Integer>());
}
list.get(level).add(node.val);
if (node.left != null) {
queue.addLast(new NodeLevel(node.left, level + 1));
}
if (node.right != null) {
queue.addLast(new NodeLevel(node.right, level + 1));
}
}
return list;
}
5、判断一棵树是否是完全二叉树
思路:
这道题也是利用层序遍历的思想,带着空节点去层序遍历。如果遇到空之后所有的节点都是空,那么它就是一棵完全二叉树。而如果遇到空之后,又遇到非空,那么它就不是完全二叉树。
具体操作是,首先判断根节点是否为空,如果为空,说明这是一棵空树,空树就是一棵完全二叉树。否则的话创建一个队列,将根节点入队列。
接下来从队列里出掉第一个元素,然后将这个元素的左右孩子都入队列,一直重复这个操作。如果遇到空,就去判断队列中剩余节点是否全为空。
遇到空节点而队列不为空的时候,出掉队列中的第一个节点,判断是否为空,如果不为空,那么这就不是一棵完全二叉树,反之就是完全二叉树。
具体代码如下:
private static boolean isComplete(TreeNode root){
if (root==null){
return true;
}
LinkedList<TreeNode> queue = new LinkedList<>();
queue.addLast(root);
while (true){
TreeNode front = queue.pollFirst();
if (front==null){
//遇到空,下一步去判断队列中剩余的节点,是否全是空
break;
}
queue.addLast(front.left);
queue.addLast(front.right);
}
//判断剩下所有节点是否是非空
while (!queue.isEmpty()){
TreeNode front = queue.pollFirst();
//意味着空遇到非空
if (front!=null){
return false;
}
}
return true;
}
6、输入一棵二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的结点,只能调整树中结点指针的指向。
思路:
搜索树中只有中序遍历是有序的。
具体操作是采用递归的方法,中序遍历这棵树,每拿到一个节点就串到双向链表上。
需要考虑两点:
1)每次拿到一个节点,如何将节点串成双向链表
2)如何有序的拿到一个节点(对于搜索树)
第二个问题很好解决,中序遍历这棵树,拿到的节点就是有序的。
第一个问题就和之前我们创建双向链表的方法一样,不过是将前驱节点prev改成树的left节点,next节点改成树的right节点。
用下面这棵树来详细说明
首先进行中序遍历,先遍历左子树,然后根节点,最后遍历右子树。
递归到A节点,把A串到链表中。A的left引用指向prev,也就是null。然后将head和prev引用都指向A节点。
再根据中序遍历拿到B节点,B的left引用指向prev,也就是A,因为prev引用不为空,所以将prev的right引用指向B,prev也指向B。
然后拿到C节点,C的left指向prev,也就是B,prev的right指向C,prev也指向C。
以此类推,最后返回head节点,就可以拿到已经转换好的双向链表。
具体代码如下:
private static TreeNode prev = null;
private static TreeNode head = null;
private static void buildDList(TreeNode node){
node.left = prev;
if (prev!=null){
prev.right = node;head
}else {
head = node;
}
prev = node;
}
private static void inOrderTraversalSearchTree(TreeNode root){
if (root!=null){
inOrderTraversalSearchTree(root.left);
buildDList(root);
inOrderTraversalSearchTree(root.right);
}
}
public TreeNode Convert(TreeNode pRootOfTree) {
prev = null;
head = null;
inOrderTraversalSearchTree(pRootOfTree);
return head;
}