(六)剑指offer 二叉树篇

本文深入探讨了二叉树的15个经典算法问题,包括重建二叉树、二叉树镜像、层次遍历、路径和、二叉树深度、平衡二叉树等,提供了详细的解题思路和代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.重建二叉树

题目

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

答案

思路:

如果对树的基础知识掌握的还不错的话,这道题的思路肯定是有的,也能想到用递归去实现它(分治法),但是不一定能写出来,其实很简单。

首先,先序遍历的第一个值,肯定是这个树的根节点的值。

然后,在中序遍历中寻找根节点的值,找到并把这个位置valIndex 记下来,则在中序遍历中,valIndex 位置左边的数就是根节点左子树的值,这些值共有valIndex -0个,右边同理有in.length - 1 - valIndex 个。

再然后,在先序遍历中,第0 + 1(包括) 到 0 + valIndex -0(包括)根节点的左子树,而且第一个值是左子树的根节点的值,同理右边是第0 + valIndex - 0 + 1(包括)到 pre.length - 1(包括)。

最后,把左边的值交给根节点的左子树,右边的值交给根节点的右子树,然后就出来了。

 	/**
	 * 重建二叉樹
	 * @param pre
	 * @param in
	 * @return
	 */
	public TreeNode reConstructBinaryTree(int[] pre, int[] in) {
		if (pre == null || in == null || pre.length == 0 || in.length == 0) {
			return null;
		}
		return reChildTree(pre, 0, pre.length - 1, in, 0, in.length - 1);
	}

	public TreeNode reChildTree(int[] pre, int preStart, int preEnd, int[] in, int inStart, int inEnd) {
		if (preStart > preEnd || inStart > inEnd) {
			return null;
		}
		int val = pre[preStart];
		TreeNode node = new TreeNode(val);

		int valIndex = 0;
		for (int i = inStart; i <= inEnd; i++) {
			if (val == in[i]) {
				valIndex = i;
				break;
			}
		}

		node.left = reChildTree(pre, preStart + 1, preStart + valIndex - inStart, in, inStart, valIndex - 1);

		node.right = reChildTree(pre, preStart + valIndex - inStart + 1, preEnd, in, valIndex + 1, inEnd);

		return node;
	}

// cpp code
	TreeNode* reConstructBinaryTree(vector<int> pre, vector<int> vin) {
		if (pre.empty() || vin.empty())
			return nullptr;
		int gen = 0;
		for (int i = 0; i < vin.size(); i++) {
			if (vin[i] == pre[0]) {
				gen = i;
				break;
			}
		}
		vector<int> pre_left, pre_right, vin_left, vin_right;
		for (int i = 0; i < gen; i++) {
			pre_left.push_back(pre[i + 1]);
			vin_left.push_back(vin[i]);
		}
		for (int i = gen + 1; i < vin.size(); i++) {
			pre_right.push_back(pre[i]);
			vin_right.push_back(vin[i]);
		}
		TreeNode *node = new TreeNode(pre[0]);
		node->left = reConstructBinaryTree(pre_left, vin_left);
		node->right = reConstructBinaryTree(pre_right, vin_right);

		return node;
	}

2.二叉树的镜像

题目

操作给定的二叉树,将其变换为源二叉树的镜像。
在这里插入图片描述

答案

思路:这个很简单的,就是从根结点遍历的时候,将root.leftroot.right变量里面的地址值交换一下,然后递归重复遍历就OK,需要注意的点就是递归的结束条件,这也是所有使用递归的程序应该注意的点。

	/**
	 * 二叉樹的鏡像
	 * 
	 * @param root
	 */
	public void Mirror(TreeNode root) {
		if (root != null) {
			TreeNode temp = root.left;
			root.left = root.right;
			root.right = temp;
			Mirror(root.left);
			Mirror(root.right);
		}
	}

// cpp code
	TreeNode* Mirror(TreeNode *pRoot) {
		// write code here
		if (!pRoot)
			return nullptr;
		TreeNode *temp = pRoot->left;
		pRoot->left = pRoot->right;
		pRoot->right = temp;
		Mirror(pRoot->left);
		Mirror(pRoot->right);
		return pRoot;
	}

3.从上往下打印二叉树(层次遍历)

题目

从上往下打印出二叉树的每个节点,同层节点从左至右打印。

答案

思路:
可以用一个队列来储存每一层的TreeNode类型的节点,比如说下面这个树:
在这里插入图片描述
首先将根节点8放到队列的队尾(这时候队列里面只有8一个元素)

然后再将队列的队头元素取出(也就是8),如果8有左节点,将左节点放到队尾,同理如果有右结点也放到队尾。

最后将取出来的队头元素放到结果集rel中。

重复上面的步骤,直到队列为空,是不是很耐斯啊。还有注意一点,这里我用的是LinkedList作为队列,而不是ArrayList,因为LinkedList使用链表实现,ArrayList使用数组实现,删除队头元素的复杂度不一样的塞,显然前者要快很多,和你一样,就一下。

	/**
	 * 从上往下打印二叉树
	 * 
	 * @param root
	 * @return
	 */
	public ArrayList<Integer> PrintFromTopToBottom(TreeNode root) {
		ArrayList<Integer> rel = new ArrayList<>();
		if (root == null)
			return rel;
		LinkedList<TreeNode> queue = new LinkedList<>();
		queue.add(root);
		while (queue.size() != 0) {
			TreeNode temp = queue.remove();
			if (temp.left != null) {
				queue.add(temp.left);
			}
			if (temp.right != null) {
				queue.add(temp.right);
			}
			rel.add(temp.val);
		}
		return rel;
	}

//cpp code
	vector<int> PrintFromTopToBottom(TreeNode *root) {
		vector<int> res;
		if (!root)
			return res;
		queue<TreeNode*> nodeQueue;
		nodeQueue.push(root);
		while (!nodeQueue.empty()) {
			TreeNode *temp = nodeQueue.front();
			res.push_back(temp->val);
			if (temp->left)
				nodeQueue.push(temp->left);
			if (temp->right)
				nodeQueue.push(temp->right);
			nodeQueue.pop();
		}
		return res;
	}

4.二叉树中和为某一值的路径(路径和)

题目

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

答案

理解了就很简单,理解不了似登天。铁子,如果要彻底看懂下面的代码,你要知道在Java中 引用传递 与 值传递 的区别,是知道的很透的那种。

思路:看代码以及注释吧,能让你理解,但是有两个要强调也可能是你看不懂的地方,我个人建议是你先看代码,如果那两个点看不懂,再翻上来看这两点说明。

  1. resultList.add(new ArrayList<>(oneList));,这一行代码为什么不是直接将oneList加入到resultList中?因为oneList是一个引用型变量,里面放的是oneList集合本身的地址值,直接将oneList加入到resultList中,相当于递归里面的所有这条语句,都是操作的同一个集合,与你的预期是不符的,所以你要重新开辟一块空间,把你想存入的值放到这块空间里面,再把这块空间的地址值放到resultList中。

  2. oneList.remove(oneList.size() - 1);这里为什么要删除掉oneList集合中的最后一个元素?还是像上面一样,这是一个关于引用传递的问题,oneList里面放的是oneList集合本身的地址值,你每一次对他的操作,都是对oneList值所对应的空间进行操作,都会使这片空间的内容发生变化。看下面这棵树,如果你将11放到oneList里面,现在8 - 11 这条路径访问完了,你要访问8 - 9,是不是要把11这个节点删除掉,再把9这个节点放进去,其他的节点都是这个道理。
    在这里插入图片描述

	/**
	 * 二叉树中和为某一值的路径
	 * @param root
	 * @param target
	 * @return
	 */
	ArrayList<ArrayList<Integer>> resultList = new ArrayList<ArrayList<Integer>>();
	ArrayList<Integer> oneList = new ArrayList<>();
	public ArrayList<ArrayList<Integer>> FindPath(TreeNode root, int target) {
		
		// 这也是递归结束的条件判断,如果说传进来的节点是一个空节点,咋们就不对他进行操作。
		if(root == null) 
			return resultList;

		oneList.add(root.val);
		target -= root.val;
		
		//如果说当前节点是叶子结点,而且这条路径和是期望结果
		if(target == 0 && root.left == null && root.right == null) 
			resultList.add(new ArrayList<>(oneList));
		
		//如果不是叶子结点,就对该节点的左节点和右结点分别递归
		FindPath(root.left, target);
		FindPath(root.right, target);
		
		//不懂就翻上去看思路那块
		oneList.remove(oneList.size() - 1);
		
		return resultList;
	}

5.二叉树的深度

题目

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

答案

思路:
只要递归玩的溜,二叉树的题基本上就没有问题的!下面的代码我该如何解释呢?还是那句话,主要是对递归结束的条件的判断。

想一下,你从根节点开始数一树的深度,到哪会停下来,对吧!当然是叶子结点,路过的非叶子结点,直接给树的深度 +1 就OK,所以循环结束的条件是当达到叶子结点的时候,返回 1,非叶子结点,就返回左子树与右子树中深度较大的那个树的深度 加上 自己本身这一节点。

你可以再想一下,如果数到叶子结点不返回1,而是继续往下数,遇到空树就返回0,其余的结点就返回左子树与右子树中深度较大的那个树的深度 加上 自己本身这一节点。道理是一样的,下面的注释,如果去掉就少一层递归,多一步判断,复杂度一样,原理也是一样的。

	/**
	 * 求二叉树深度
	 * @param root
	 * @return
	 */
	 public int TreeDepth(TreeNode root) {
		 if(root == null)
			 return 0;
//		 if(root.left == null && root.right == null)
//			 return 1;
		 return TreeDepth(root.left) > TreeDepth(root.right) ? TreeDepth(root.left) + 1 : TreeDepth(root.right) + 1;
	 }

6.平衡二叉树

题目

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

答案

思路:

你首先要是知道什么是平衡二叉树吧!
平衡二叉树(Balanced Binary Tree)具有以下性质:它是一棵空树或它的左右两个子树的高度差的绝对值不超过1,并且左右两个子树都是一棵平衡二叉树。

这样是不是就简单了啊,只要求出一个树的左右子树的高度差,就能知道这个树是不是平衡二叉树,求树的高度上一道题就是啊。

	 /**
	  * 平衡二叉树
	  * @param root
	  * @return
	  */
	 public boolean IsBalanced_Solution(TreeNode root) {
		 if(root == null)
			 return true;
		 if(Math.abs(TreeDepth(root.left) - TreeDepth(root.right)) <= 1)
			 return true;
		return false;
	 }
	 //求一个数的深度
	 public int TreeDepth(TreeNode root) {
		 if(root == null)
			 return 0;
		 return TreeDepth(root.left) > TreeDepth(root.right) ? TreeDepth(root.left) + 1 : TreeDepth(root.right) + 1;
	 }

7.二叉树的下一个结点

题目

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

答案

思路:

首先我们要知道什么是中序遍历,在二叉树中,中序遍历首先遍历左子树,然后访问根结点,最后遍历右子树。所以说:

  1. 如果一个节点有右子树,则右子树的最左边的节点,就是这个节点的后继元素。
  2. 如果一个节点没有右子树,就要看他是父节点的左孩子还是右孩子,如果是左孩子,父节点就是他的后继元素,如果是右孩子,就要看他的父节点是父父节点的左孩子还是右孩子,如果是左孩子,父父节点就是他的后继元素,如果是右孩子……
  3. 重复上面的第二步骤,直到存放父节点的nextnull,也就是说已经到根节点,就返回null,因为这个节点或者是二叉树中序遍历的最后一个节点,或者是不在这个树里面。
	/**
	 * 二叉树的下一个结点
	 * 
	 * @param pNode
	 * @return
	 */
	public TreeLinkNode GetNext(TreeLinkNode pNode) {
		if (pNode == null)
			return null;
		// 如果他有右子树
		if (pNode.right != null) {
			pNode = pNode.right;
			while (pNode.left != null)
				pNode = pNode.left;
			return pNode;
		}
		// 如果没有右子树
		while (pNode.next != null) {
			if (pNode.next.left == pNode) {
				return pNode.next;
			} else {
				pNode = pNode.next;
			}
		}
		return null;
	}

8.对称的二叉树

题目

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

答案

思路:我刚开始总共想到了三种方法

  1. 求出这个二叉树的镜像树,然后再和二叉树比较,如果两个树相等,就是对称的,如果不相等,就不是对称的。显然这样的话,不是最好的,求一边镜像树,再比教一遍,花费的时间太多了。
  2. 把根节点的左右子树分别进行层次遍历,左边的从左到右遍历,右边的从右到左遍历,然后在一一比较,这个代码看上去就比较麻烦,时间复杂度却很低,只有O(n),但是要借用两个队列的额外空间。看代码一
  3. 最后一种,递归比较二叉树的对称位置。看代码二!

	/**
	 * 代碼一
	 * @param pRoot
	 * @return
	 */
	public boolean isSymmetrical(TreeNode pRoot) {
		if (pRoot == null)
			return true;
		if (pRoot.left == null && pRoot.right == null)
			return true;
		if (pRoot.left != null && pRoot.right != null) {
			LinkedList<TreeNode> leftList = new LinkedList<>();
			LinkedList<TreeNode> rightList = new LinkedList<>();
			leftList.add(pRoot.left);
			rightList.add(pRoot.right);
			while (leftList.size() > 0 && rightList.size() > 0) {
				if (leftList.getFirst().left != null && rightList.getFirst().right != null) {
					leftList.add(leftList.getFirst().left);
					rightList.add(rightList.getFirst().right);
				}else if(leftList.getFirst().left == null && rightList.getFirst().right == null) {
				}else {
					return false;
				}
				if (leftList.getFirst().right != null && rightList.getFirst().left != null) {
					leftList.add(leftList.getFirst().right);
					rightList.add(rightList.getFirst().left);
				}else if(leftList.getFirst().right == null && rightList.getFirst().left == null) {
				}else {
					return false;
				}
				if (leftList.remove().val != rightList.remove().val)
					return false;
			}
			if (leftList.size() != rightList.size()) {
				return false;
			}else {
				return true;
			}
		}
		return false;
	}
	/**
	 * 对称二叉树
	 * 代码二
	 * @param pRoot
	 * @return
	 */
	public boolean isSymmetrical(TreeNode pRoot) {
		if (pRoot == null)
			return true;
		return isEquals(pRoot.left, pRoot.right);
	}
	public boolean isEquals(TreeNode left, TreeNode right) {
		if (left == null && right == null)
			return true;
		if (left != null && right != null) {
			if (left.val != right.val)
				return false;
			return isEquals(left.left, right.right) && isEquals(left.right, right.left);
		}
		return false;
	}

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

题目

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

答案

思路:挺好理解的,用两个栈就完美解决。

首先,将根节点放到第一个栈里面。

然后:

  1. 将第一个栈的栈顶元素的左子树右子树分别放到第二个栈里面(注意顺序),然后把栈顶元素保存并删除,循环这一步,直到第一个栈为空。
  2. 将第二个栈的栈顶元素的右子树左子树分别放到第一个栈里面(注意顺序),然后把栈顶元素保存并删除,循环这一步,直到第二个栈为空。
  3. 循环上面两步,直到两个栈都为空。
	/**
	 * 按之字形顺序打印二叉树
	 * 
	 * @param pRoot
	 * @return
	 */
	public ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
		ArrayList<ArrayList<Integer>> allList = new ArrayList<>();
		if (pRoot == null)
			return allList;
		Stack<TreeNode> ltr = new Stack<>();
		Stack<TreeNode> rtl = new Stack<>();
		ltr.push(pRoot);
		while (!ltr.empty() || !rtl.empty()) {
			ArrayList<Integer> list1 = new ArrayList<>();
			while (!ltr.empty()) {
				if (ltr.peek().left != null)
					rtl.push(ltr.peek().left);
				if (ltr.peek().right != null)
					rtl.push(ltr.peek().right);
				list1.add(ltr.pop().val);
			}
			if (!list1.isEmpty())
				allList.add(new ArrayList<>(list1));

			ArrayList<Integer> list2 = new ArrayList<>();
			while (!rtl.empty()) {
				if (rtl.peek().right != null)
					ltr.push(rtl.peek().right);
				if (rtl.peek().left != null)
					ltr.push(rtl.peek().left);
				list2.add(rtl.pop().val);
			}
			if (!list2.isEmpty())
				allList.add(new ArrayList<>(list2));
		}
		return allList;
	}

10.把二叉树打印成多行

题目

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

答案

思路:有好多种方法,递归,循环都可以。

  1. 可以用两个队列,来回交替,按层输出。
  2. 也可以用一个队列,记录每一层的结点数,进行输出,两个思路其实是一样的。

最好是把下面两个代码比较一下,感受一下他们的细微区别之处。

	/**
	 * 把二叉树打印成多行
	 * @param pRoot
	 * @return
	 */
	ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
		ArrayList<ArrayList<Integer>> allList = new ArrayList<>();
		if (pRoot == null)
			return allList;
		LinkedList<TreeNode> queue1 = new LinkedList<>();
		LinkedList<TreeNode> queue2 = new LinkedList<>();
		queue1.add(pRoot);
		while (!queue1.isEmpty() || !queue2.isEmpty()) {
			ArrayList<Integer> list1 = new ArrayList<>();
			while (!queue1.isEmpty()) {
				if (queue1.getFirst().left != null)
					queue2.add(queue1.getFirst().left);
				if (queue1.getFirst().right != null)
					queue2.add(queue1.getFirst().right);
				list1.add(queue1.remove().val);
			}
			if (!list1.isEmpty())
				allList.add(new ArrayList<>(list1));
			ArrayList<Integer> list2 = new ArrayList<>();
			while (!queue2.isEmpty()) {
				if (queue2.getFirst().left != null)
					queue1.add(queue2.getFirst().left);
				if (queue2.getFirst().right != null)
					queue1.add(queue2.getFirst().right);
				list2.add(queue2.remove().val);
			}
			if (!list2.isEmpty())
				allList.add(new ArrayList<>(list2));
		}
		return allList;
	}
	/**
	 * 把二叉树打印成多行
	 * 
	 * @param pRoot
	 * @return
	 */
	ArrayList<ArrayList<Integer>> Print(TreeNode pRoot) {
		ArrayList<ArrayList<Integer>> allList = new ArrayList<>();
		ArrayList<Integer> list = new ArrayList<>();
		Queue<TreeNode> queue = new LinkedList<>();
		if(pRoot == null)
			return allList;
		queue.add(pRoot);
		int start = 0, end = 1;
		while(!queue.isEmpty()) {
			if(queue.peek().left != null)
				queue.add(queue.peek().left);
			if(queue.peek().right != null)
				queue.add(queue.peek().right);
			list.add(queue.poll().val);
			start++;
			if(start == end) {
				start = 0;
				end = queue.size();
				allList.add(list);
				list = new ArrayList<>();
			}
		}
		return allList;
	}

11.序列化二叉树

题目

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

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

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

答案

思路:感受一下,为什么要用队列,你就可以发现,这个队列用的实在是太巧妙了!!!哈哈哈,跟着代码屡一下思路,不难理解,下面使用先序遍历实现的,中序和后序一样,如果用层次遍历,就更好写了,也用一个队列不用递归,循环就可以搞定。

	/**
	 * 序列化二叉树
	 * 
	 * @param root
	 * @return
	 */
	String Serialize(TreeNode root) {
		if (root == null)
			return "#!";
		return root.val + "!" + Serialize(root.left) + Serialize(root.right);
	}
	/**
	 * 反序列化二叉树
	 * 
	 * @param str
	 * @return
	 */
	TreeNode Deserialize(String str) {
		String[] strA = str.split("!");
		Queue<String> queue = new LinkedList<>();
		for (int i = 0; i < strA.length; i++)
			queue.offer(strA[i]);
		return destTree(queue);
	}
	TreeNode destTree(Queue<String> queue) {
		String val = queue.poll();
		if(val.equals("#"))
			return null;
		TreeNode head = new TreeNode(Integer.valueOf(val));
		head.left = destTree(queue);
		head.right = destTree(queue);
		return head;
	}

12.树的子结构

题目

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

	     1         2
        / \         \
       2   3         5
      /	\ 	\
     4   5   6A)       (B)

答案

思路:
HasSubtree(TreeNode root1, TreeNode root2) 的作用就是,找出A中与B的根节点相等的节点,然后把找出的节点和B的根节点传给isSubtree方法。

isSubtree(TreeNode root1, TreeNode root2) 这个方法的作用就是,当A的某个节点和B的根节点相等的时候,开始用它判断B是不是A的子结构。

深深体会sign 存在的意义,非常有意思,我写的,我不骄傲。小小提示一下,看下面:

    	 8          8
        / \        / \ 
       8   7      9   2   
      /	\ 	\
     9   2   6A)       (B)

代码实现:

	/**
	 * 树的子结构
	 * 
	 * @param root1
	 * @param root2
	 * @return
	 */
	public boolean HasSubtree(TreeNode root1, TreeNode root2) {
		if (root1 == null || root2 == null)
			return false;
		boolean sign = false;
		if (root1.val == root2.val) 
			 sign = isSubtree(root1, root2);
		if (!sign)
			return HasSubtree(root1.left, root2) || HasSubtree(root1.right, root2);
		return sign;
	}
	public 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);
	}

13.二叉搜索树的后序遍历序列

题目

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

答案

这道是我做的最开心最开心的一道题,拿到题就开始思考,然后就写,写完没有进行逻辑复查就AC,竟然一次就过了,开心的像个孩子。

思路:看下面!
在这里插入图片描述
有么有发现,最后一个总是比前半段大,比后半段小,而且132和576,都是如此!感受一下mid存在价值,非同凡响。

代码如下:

	/**
	 * 二叉搜索树的后序遍历序列
	 * 
	 * @param sequence
	 * @return
	 */
	public boolean VerifySquenceOfBST(int[] sequence) {
		if (sequence == null || sequence.length == 0)
			return false;
		return isBST(sequence, 0, sequence.length - 1);
	}
	public boolean isBST(int[] sequence, int start, int end) {
		if (start == end)
			return true;
		int cur = sequence[end];
		int mid = -1;
		for (int i = start; i < end; i++) {
			if (mid == -1 && sequence[i] > cur)
				mid = i;
			if (mid != -1 && sequence[i] < cur)
				return false;
		}
		if (mid == -1 || mid == start) {//只有前半段或者只有后半段
			return isBST(sequence, start, end - 1);
		} else {
			return isBST(sequence, start, mid - 1) && isBST(sequence, mid, end - 1);
		}
	}

14.二叉搜索树与双向链表

题目

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

答案

思路:

跟着这个推一遍吧,在没有找到 1 之前temp始终为null,这是利用递归本身的属性,看代码中的注释,应该就明白了。

    	 4         
        / \        
       2   6        
      /	\ 	\
     1   3   7     

代码如下:

	/**
	 * 二叉搜索树与双向链表
	 * 
	 * @param pRootOfTree
	 * @return
	 */
	TreeNode temp = null;// 临时交换变量
	TreeNode resHead = null;// 存放双街表的第一个节点

	public TreeNode Convert(TreeNode pRootOfTree) {
		convertLink(pRootOfTree);
		return resHead;
	}

	void convertLink(TreeNode pRootOfTree) {
		if (pRootOfTree == null)
			return;
		convertLink(pRootOfTree.left);//等这个递归执行结束,才会执行下面语句
		if (temp == null) {
			temp = pRootOfTree;
			resHead = pRootOfTree;
		} else {
			temp.right = pRootOfTree;
			pRootOfTree.left = temp;
			temp = pRootOfTree;
		}
		convertLink(pRootOfTree.right);//上面的执行完之后,才会执行这个递归
	}

15.二叉搜索树的第k个结点

题目

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

答案

思路:总共用了下面三种方法,这种题做多了,感觉都一样。

第一种:使用了一个全局标记变量,当这个变量和k相等时,返回当前结点就OK。这个方法返回的结果只有两个,要么是目标结点,要么是null,这也就是为什么要判断leftright了。

第二种:用了一个栈,也挺好理解的,跟着代码撸一遍思路,就理解了。

第三种:最好理解的一种,也最好写,和第一种整体思路方向是一样的,只不过这个是把中序遍历的结果放在一个数组里面,最后在对数组进行访问。

	/**
	 * 二叉搜索树的第k个结点
	 * @param pRoot
	 * @param k
	 * @return
	 */
	int count = 0;
	TreeNode KthNode(TreeNode pRoot, int k) {
		if (pRoot != null) {
			TreeNode left = KthNode(pRoot.left, k);
			count++;
			if (left != null)
				return left;
			if (count == k)
				return pRoot;
			TreeNode right = KthNode(pRoot.right, k);
			if (right != null)
				return right;
		}
		return null;
	}
	/**
	 * 二叉搜索树的第k个结点
	 * 
	 * @param pRoot
	 * @param k
	 * @return
	 */
	TreeNode KthNode(TreeNode pRoot, int k) {
		Stack<TreeNode> stack = new Stack<>();
		int count = 0;
		while(pRoot != null || stack.size() != 0) {
			while(pRoot != null) {
				stack.push(pRoot);
				pRoot = pRoot.left;
			}
			pRoot = stack.pop();
			count++;
			if(count == k)
				return pRoot;
			if(pRoot != null) 
				pRoot = pRoot.right;
		}
		return null;
	}
	/**
	 * 二叉搜索树的第k个结点
	 * 
	 * @param pRoot
	 * @param k
	 * @return
	 */
	TreeNode KthNode(TreeNode pRoot, int k) {
		ArrayList<TreeNode> array = new ArrayList<>();
		nodeToArray(pRoot, array);
		if (k > array.size() || k < 1)
			return null;
		return array.get(k - 1);
	}

	void nodeToArray(TreeNode pRoot, ArrayList<TreeNode> array) {
		if (pRoot != null) {
			nodeToArray(pRoot.left, array);
			array.add(pRoot);
			nodeToArray(pRoot.right, array);
		}
	}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

yelvens

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值