面试中所有二叉树题目总结(java版)

面试大总结之二:Java搞定面试中的二叉树题目 这是本系列的第二篇,与前一篇 面试大总结之一:Java搞定面试中的链表题目 相比,二叉树的题目可以变化的就更多了。本文还是参考整合重写了《轻松搞定面试中的二叉树题目》和《算法大全(3) 二叉树》两篇大作。本文一个小亮点就是几乎每一道题都用了递归和迭代两种方法写过一遍,因为面试时往往可能会被要求写不擅长的那一种。这一千多行的记录也是我在面试摸索过程中的一个小笔记,备份与此。请大神们轻拍指正。今后 阅读详情
<span style="font-size:18px;">package BinaryTreeSummary;

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

/**
 * http://blog.youkuaiyun.com/luckyxiaoqiang/article/details/7518888  轻松搞定面试中的二叉树题目
 * http://www.cnblogs.com/Jax/archive/2009/12/28/1633691.html  算法大全(3) 二叉树
 * 
 * TODO: 一定要能熟练地写出所有问题的递归和非递归做法!
 *
 * 1. 求二叉树中的节点个数: getNodeNumRec(递归),getNodeNum(迭代)
 * 2. 求二叉树的深度: getDepthRec(递归),getDepth 
 * 3. 前序遍历,中序遍历,后序遍历: preorderTraversalRec, preorderTraversal, inorderTraversalRec, postorderTraversalRec
 * (https://en.wikipedia.org/wiki/Tree_traversal#Pre-order_2)
 * 4.分层遍历二叉树(按层次从上往下,从左往右): levelTraversal, levelTraversalRec(递归解法!)
 * 5. 将二叉查找树变为有序的双向链表: convertBST2DLLRec, convertBST2DLL
 * 6. 求二叉树第K层的节点个数:getNodeNumKthLevelRec, getNodeNumKthLevel
 * 7. 求二叉树中叶子节点的个数:getNodeNumLeafRec, getNodeNumLeaf
 * 8. 判断两棵二叉树是否相同的树:isSameRec, isSame
 * 9. 判断二叉树是不是平衡二叉树:isAVLRec
 * 10. 求二叉树的镜像(破坏和不破坏原来的树两种情况):mirrorRec, mirrorCopyRec
 * 10.1 判断两个树是否互相镜像:isMirrorRec
 * 11. 求二叉树中两个节点的最低公共祖先节点:getLastCommonParent, getLastCommonParentRec, getLastCommonParentRec2
 * 12. 求二叉树中节点的最大距离:getMaxDistanceRec
 * 13. 由前序遍历序列和中序遍历序列重建二叉树:rebuildBinaryTreeRec
 * 14.判断二叉树是不是完全二叉树:isCompleteBinaryTree, isCompleteBinaryTreeRec
 * 
 */
public class Demo {

	/*
	 			 1 
		        / \ 
		       2   3 
		      / \   \ 
		     4  5   6 
	 */
	public static void main(String[] args) {
		TreeNode r1 = new TreeNode(1);
		TreeNode r2 = new TreeNode(2);
		TreeNode r3 = new TreeNode(3);
		TreeNode r4 = new TreeNode(4);
		TreeNode r5 = new TreeNode(5);
		TreeNode r6 = new TreeNode(6);
		
		r1.left = r2;
		r1.right = r3;
		r2.left = r4;
		r2.right = r5;
		r3.right = r6;
		
//		System.out.println(getNodeNumRec(r1));
//		System.out.println(getNodeNum(r1));
//		System.out.println(getDepthRec(r1));
//		System.out.println(getDepth(r1));
		
//		preorderTraversalRec(r1);
//		System.out.println();
//		preorderTraversal(r1);
//		System.out.println();
//		inorderTraversalRec(r1);
//		System.out.println();
//		inorderTraversal(r1);
//		System.out.println();
//		postorderTraversalRec(r1);
//		System.out.println();
//		postorderTraversal(r1);
//		System.out.println();
//		levelTraversal(r1);
//		System.out.println();
//		levelTraversalRec(r1);
//		System.out.println();
		
//		TreeNode tmp = convertBSTRec(r1);
//		while(true){
//			if(tmp == null){
//				break;
//			}
//			System.out.print(tmp.val + " ");
//			if(tmp.right == null){
//				break;
//			}
//			tmp = tmp.right;
//		}
//		System.out.println();
//		while(true){
//			if(tmp == null){
//				break;
//			}
//			System.out.print(tmp.val + " ");
//			if(tmp.left == null){
//				break;
//			}
//			tmp = tmp.left;
//		}
		
		
//		TreeNode tmp = convertBST2DLL(r1);
//		while(true){
//			if(tmp == null){
//				break;
//			}
//			System.out.print(tmp.val + " ");
//			if(tmp.right == null){
//				break;
//			}
//			tmp = tmp.right;
//		}
		
//		System.out.println(getNodeNumKthLevelRec(r1, 2));
//		System.out.println(getNodeNumKthLevel(r1, 2));
		
//		System.out.println(getNodeNumLeafRec(r1));
//		System.out.println(getNodeNumLeaf(r1));
		
//		System.out.println(isSame(r1, r1));
//		inorderTraversal(r1);
//		System.out.println();
//		mirror(r1);
//		TreeNode mirrorRoot = mirrorCopy(r1);
//		inorderTraversal(mirrorRoot);
		
		System.out.println(isCompleteBinaryTree(r1));
		System.out.println(isCompleteBinaryTreeRec(r1));
		
	}

	private static class TreeNode {
		int val;
		TreeNode left;
		TreeNode right;

		public TreeNode(int val) {
			this.val = val;
		}
	}

	/**
	 * 求二叉树中的节点个数递归解法: O(n)
	 * (1)如果二叉树为空,节点个数为0 
	 * (2)如果二叉树不为空,二叉树节点个数 = 左子树节点个数 +
	 *    	      右子树节点个数 + 1
	 */
	public static int getNodeNumRec(TreeNode root) {
		if (root == null) {
			return 0;
		} else {
			return getNodeNumRec(root.left) + getNodeNumRec(root.right) + 1;
		}
	}
	
	/**
	 *  求二叉树中的节点个数迭代解法O(n):基本思想同LevelOrderTraversal,
	 *  即用一个Queue,在Java里面可以用LinkedList来模拟 
	 */
	public static int getNodeNum(TreeNode root) {
		if(root == null){
			return 0;
		}
		int count = 1;
		Queue<TreeNode> queue = new LinkedList<TreeNode>();
		queue.add(root);
		
		while(!queue.isEmpty()){
			TreeNode cur = queue.remove();		// 从队头位置移除
			if(cur.left != null){			// 如果有左孩子,加到队尾
				queue.add(cur.left);
				count++;
			}
			if(cur.right != null){		// 如果有右孩子,加到队尾
				queue.add(cur.right);
				count++;
			}
		}
		
		return count;
	}

	/**
	 * 求二叉树的深度(高度) 递归解法: O(n)
	 * (1)如果二叉树为空,二叉树的深度为0 
	 * (2)如果二叉树不为空,二叉树的深度 = max(左子树深度, 右子树深度) + 1
	 */
	public static int getDepthRec(TreeNode root) {
		if (root == null) {
			return 0;
		}

		int leftDepth = getDepthRec(root.left);
		int rightDepth = getDepthRec(root.right);
		return Math.max(leftDepth, rightDepth) + 1;
	}
	
	/**
	 * 求二叉树的深度(高度) 迭代解法: O(n)
	 * 基本思想同LevelOrderTraversal,还是用一个Queue
	 */
	public static int getDepth(TreeNode root) {
		if(root == null){
			return 0;
		}
		
		int depth = 0;							// 深度
		int currentLevelNodes = 1;		// 当前Level,node的数量
		int nextLevelNodes = 0;			// 下一层Level,node的数量
		
		LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
		queue.add(root);
		
		while( !queue.isEmpty() ){
			TreeNode cur = queue.remove();		// 从队头位置移除
			currentLevelNodes--;			// 减少当前Level node的数量
			if(cur.left != null){				// 如果有左孩子,加到队尾
				queue.add(cur.left);
				nextLevelNodes++;			// 并增加下一层Level node的数量
			}
			if(cur.right != null){			// 如果有右孩子,加到队尾
				queue.add(cur.right);
				nextLevelNodes++;
			}
			
			if(currentLevelNodes == 0){ // 说明已经遍历完当前层的所有节点
				depth++;					   // 增加高度
				currentLevelNodes = nextLevelNodes;		// 初始化下一层的遍历
				nextLevelNodes = 0;
			}
		}
		
		return depth;
	}
	
	

	/**
	 * 前序遍历,中序遍历,后序遍历 前序遍历递归解法: 
	 * (1)如果二叉树为空,空操作 
	 * (2)如果二叉树不为空,访问根节点,前序遍历左子树,前序遍历右子树
	 */
	public static void preorderTraversalRec(TreeNode root) {
		if (root == null) {
			return;
		}
		System.out.print(root.val + " ");
		preorderTraversalRec(root.left);
		preorderTraversalRec(root.right);
	}
	
	/**
	 *  前序遍历迭代解法:用一个辅助stack,总是把右孩子放进栈
	 *  http://www.youtube.com/watch?v=uPTCbdHSFg4
	 */
	public static void preorderTraversal(TreeNode root) {
		if(root == null){
			return;
		}
		
		Stack<TreeNode> stack = new Stack<TreeNode>();		// 辅助stack
		stack.push(root);
		
		while( !stack.isEmpty() ){
			TreeNode cur = stack.pop();		// 出栈栈顶元素
			System.out.print(cur.val + " ");
			
			// 关键点:要先压入右孩子,再压入左孩子,这样在出栈时会先打印左孩子再打印右孩子
			if(cur.right != null){
				stack.push(cur.right);
			}
			if(cur.left != null){
				stack.push(cur.left);
			}
		}
	}

	/**
	 * 中序遍历递归解法 
	 * (1)如果二叉树为空,空操作。 
	 * (2)如果二叉树不为空,中序遍历左子树,访问根节点,中序遍历右子树
	 */
	public static void inorderTraversalRec(TreeNode root) {
		if (root == null) {
			return;
		}
		inorderTraversalRec(root.left);
		System.out.print(root.val + " ");
		inorderTraversalRec(root.right);
	}
	
	/**
	 * 中序遍历迭代解法 ,用栈先把根节点的所有左孩子都添加到栈内,
	 * 然后输出栈顶元素,再处理栈顶元素的右子树
	 * http://www.youtube.com/watch?v=50v1sJkjxoc
	 * 
	 * 还有一种方法能不用递归和栈,基于线索二叉树的方法,较麻烦以后补上
	 * http://www.geeksforgeeks.org/inorder-tree-traversal-without-recursion-and-without-stack/
	 */
	public static void inorderTraversal(TreeNode root){
		if(root == null){
			return;
		}
		Stack<TreeNode> stack = new Stack<TreeNode>();
		TreeNode cur = root;
		
		while( true ){
			while(cur != null){		// 先添加一个非空节点所有的左孩子到栈
				stack.push(cur);
				cur = cur.left;
			}
			
			if(stack.isEmpty()){
				break;
			}
				
			// 因为此时已经没有左孩子了,所以输出栈顶元素
			cur = stack.pop();
			System.out.print(cur.val + " ");
			cur = cur.right;	// 准备处理右子树
		}
	}

	/**
	 * 后序遍历递归解法 
	 * (1)如果二叉树为空,空操作 
	 * (2)如果二叉树不为空,后序遍历左子树,后序遍历右子树,访问根节点
	 */
	public static void postorderTraversalRec(TreeNode root) {
		if (root == null) {
			return;
		}
		postorderTraversalRec(root.left);
		postorderTraversalRec(root.right);
		System.out.print(root.val + " ");
	}
	
	/**
	 *  后序遍历迭代解法
	 *  http://www.youtube.com/watch?v=hv-mJUs5mvU
	 *  
	 */
	public static void postorderTraversal(TreeNode root) {
		if (root == null) {
			return;
		}
		
		Stack<TreeNode> s = new Stack<TreeNode>();		// 第一个stack用于添加node和它的左右孩子
		Stack<TreeNode> output = new Stack<TreeNode>();// 第二个stack用于翻转第一个stack输出
		
		s.push(root);
		while( !s.isEmpty() ){		// 确保所有元素都被翻转转移到第二个stack
			TreeNode cur = s.pop();	// 把栈顶元素添加到第二个stack
			output.push(cur);		
			
			if(cur.left != null){		// 把栈顶元素的左孩子和右孩子分别添加入第一个stack
				s.push(cur.left);
			}
			if(cur.right != null){
				s.push(cur.right);
			}
		}
		
		while( !output.isEmpty() ){	// 遍历输出第二个stack,即为后序遍历
			System.out.print(output.pop().val + " ");
		}
	}

	/**
	 * 分层遍历二叉树(按层次从上往下,从左往右)迭代
	 * 相当于广度优先搜索,使用队列实现。队列初始化,将根节点压入队列。当队列不为空,进行如下操作:弹出一个节点
	 * ,访问,若左子节点或右子节点不为空,将其压入队列
	 */
	public static void levelTraversal(TreeNode root) {
		if (root == null) {
			return;
		}
		LinkedList<TreeNode> queue = new LinkedList<TreeNode>();
		queue.push(root);

		while (!queue.isEmpty()) {
			TreeNode cur = queue.removeFirst();
			System.out.print(cur.val + " ");
			if (cur.left != null) {
				queue.add(cur.left);
			}
			if (cur.right != null) {
				queue.add(cur.right);
			}
		}
	}
	
	/**
	 *  分层遍历二叉树(递归)
	 *  很少有人会用递归去做level traversal
	 *  基本思想是用一个大的ArrayList,里面包含了每一层的ArrayList。
	 *  大的ArrayList的size和level有关系
	 *  
	 *  这是我目前见到的最好的递归解法!
	 *  http://discuss.leetcode.com/questions/49/binary-tree-level-order-traversal#answer-container-2543
	 */
	public static void levelTraversalRec(TreeNode root) {
		ArrayList<ArrayList<Integer>> ret = new ArrayList<ArrayList<Integer>>();
		dfs(root, 0, ret);
		System.out.println(ret);
	}
	
	private static void dfs(TreeNode root, int level, ArrayList<ArrayList<Integer>> ret){
		if(root == null){
			return;
		}
		
		// 添加一个新的ArrayList表示新的一层
		if(level >= ret.size()){
			ret.add(new ArrayList<Integer>());
		}
		
		ret.get(level).add(root.val);	// 把节点添加到表示那一层的ArrayList里
		dfs(root.left, level+1, ret);		// 递归处理下一层的左子树和右子树
		dfs(root.right, level+1, ret);
	}
	

	/**
	 * 将二叉查找树变为有序的双向链表 要求不能创建新节点,只调整指针。 
	 * 递归解法:
	 * 参考了http://stackoverflow.com/questions/11511898/converting-a-binary-search-tree-to-doubly-linked-list#answer-11530016
	 * 感觉是最清晰的递归解法,但要注意递归完,root会在链表的中间位置,因此要手动
	 * 把root移到链表头或链表尾
	 */
	public static TreeNode convertBST2DLLRec(TreeNode root) {
		root = convertBST2DLLSubRec(root);
		
		// root会在链表的中间位置,因此要手动把root移到链表头
		while(root.left != null){
			root = root.left;
		}
		return root;
	}
	
	/**
	 *  递归转换BST为双向链表(DLL)
	 */
	public static TreeNode convertBST2DLLSubRec(TreeNode root){
		if(root==null || (root.left==null && root.right==null)){
			return root;
		}
		
		TreeNode tmp = null;
		if(root.left != null){			// 处理左子树
			tmp = convertBST2DLLSubRec(root.left);
			while(tmp.right != null){	// 寻找最右节点
				tmp = tmp.right;
			}
			tmp.right = root;		// 把左子树处理后结果和root连接
			root.left = tmp;
		}
		if(root.right != null){		// 处理右子树
			tmp = convertBST2DLLSubRec(root.right);
			while(tmp.left != null){	// 寻找最左节点
				tmp = tmp.left;
			}
			tmp.left = root;		// 把右子树处理后结果和root连接
			root.right = tmp;
		}
		return root;
	}
	
	/**
	 * 将二叉查找树变为有序的双向链表 迭代解法
//	 * 类似inorder traversal的做法
	 */
	public static TreeNode convertBST2DLL(TreeNode root) {
		if(root == null){
			return null;
		}
		Stack<TreeNode> stack = new Stack<TreeNode>();
		TreeNode cur = root;		// 指向当前处理节点
		TreeNode old = null;			// 指向前一个处理的节点
		TreeNode head = null;		// 链表头
		
		while( true ){
			while(cur != null){		// 先添加一个非空节点所有的左孩子到栈
				stack.push(cur);
				cur = cur.left;
			}
			
			if(stack.isEmpty()){
				break;
			}
				
			// 因为此时已经没有左孩子了,所以输出栈顶元素
			cur = stack.pop();
			if(old != null){
				old.right = cur;
			}
			if(head == null){		// /第一个节点为双向链表头节点
				head = cur;
			}
			
			old = cur;			// 更新old
			cur = cur.right;	// 准备处理右子树
		}
		
		return head;
	}

	/**
	 * 求二叉树第K层的节点个数   递归解法: 
	 * (1)如果二叉树为空或者k<1返回0
	 * (2)如果二叉树不为空并且k==1,返回1
	 * (3)如果二叉树不为空且k>1,返回root左子树中k-1层的节点个数与root右子树k-1层节点个数之和
	 * 
	 * 求以root为根的k层节点数目 等价于 求以root左孩子为根的k-1层(因为少了root那一层)节点数目 加上
	 * 以root右孩子为根的k-1层(因为少了root那一层)节点数目
	 * 
	 * 所以遇到树,先把它拆成左子树和右子树,把问题降解
	 * 
	 */
	public static int getNodeNumKthLevelRec(TreeNode root, int k) {
		if (root == null || k < 1) {
			return 0;
		}

		if (k == 1) {
			return 1;
		}
		int numLeft = getNodeNumKthLevelRec(root.left, k - 1); 		// 求root左子树的k-1层节点数
		int numRight = getNodeNumKthLevelRec(root.right, k - 1); 	// 求root右子树的k-1层节点数
		return numLeft + numRight;
	}
	
	/**
	 *  求二叉树第K层的节点个数   迭代解法: 
	 *  同getDepth的迭代解法
	 */
	public static int getNodeNumKthLevel(TreeNode root, int k){
		if(root == null){
			return 0;
		}
		Queue<TreeNode> queue = new LinkedList<TreeNode>();
		queue.add(root);
		
		int i = 1;
		int currentLevelNodes = 1;		// 当前Level,node的数量
		int nextLevelNodes = 0;			// 下一层Level,node的数量
		
		while( !queue.isEmpty() && i<k){
			TreeNode cur = queue.remove();		// 从队头位置移除
			currentLevelNodes--;			// 减少当前Level node的数量
			if(cur.left != null){				// 如果有左孩子,加到队尾
				queue.add(cur.left);
				nextLevelNodes++;			// 并增加下一层Level node的数量
			}
			if(cur.right != null){			// 如果有右孩子,加到队尾
				queue.add(cur.right);
				nextLevelNodes++;
			}
			
			if(currentLevelNodes == 0){ // 说明已经遍历完当前层的所有节点
				currentLevelNodes = nextLevelNodes;		// 初始化下一层的遍历
				nextLevelNodes = 0;
				i++;			// 进入到下一层
			}
		}
		
		return currentLevelNodes;
	}

	/**
	 * 求二叉树中叶子节点的个数(递归)
	 */
	public static int getNodeNumLeafRec(TreeNode root) {
		// 当root不存在,返回空
		if (root == null) {
			return 0;
		}

		// 当为叶子节点时返回1
		if (root.left == null && root.right == null) {
			return 1;
		}

		// 把一个树拆成左子树和右子树之和,原理同上一题
		return getNodeNumLeafRec(root.left) + getNodeNumLeafRec(root.right);
	}
	
	/**
	 *  求二叉树中叶子节点的个数(迭代)
	 *  还是基于Level order traversal
	 */
	public static int getNodeNumLeaf(TreeNode root) {
		if(root == null){
			return 0;
		}
		Queue<TreeNode> queue = new LinkedList<TreeNode>();
		queue.add(root);
		
		int leafNodes = 0;				// 记录上一个Level,node的数量
		
		while( !queue.isEmpty() ){
			TreeNode cur = queue.remove();		// 从队头位置移除
			if(cur.left != null){				// 如果有左孩子,加到队尾
				queue.add(cur.left);
			}
			if(cur.right != null){				// 如果有右孩子,加到队尾
				queue.add(cur.right);
			}
			if(cur.left==null && cur.right==null){			// 叶子节点
				leafNodes++;
			}
		}
		
		return leafNodes;
	}

	/**
	 * 判断两棵二叉树是否相同的树。
	 * 递归解法: 
	 * (1)如果两棵二叉树都为空,返回真
	 * (2)如果两棵二叉树一棵为空,另一棵不为空,返回假 
	 * (3)如果两棵二叉树都不为空,如果对应的左子树和右子树都同构返回真,其他返回假
	 */
	public static boolean isSameRec(TreeNode r1, TreeNode r2) {
		// 如果两棵二叉树都为空,返回真
		if (r1 == null && r2 == null) {
			return true;
		}
		// 如果两棵二叉树一棵为空,另一棵不为空,返回假
		else if (r1 == null || r2 == null) {
			return false;
		}

		if(r1.val != r2.val){
			return false;
		}
		boolean leftRes = isSameRec(r1.left, r2.left); 		// 比较对应左子树
		boolean rightRes = isSameRec(r1.right, r2.right); // 比较对应右子树
		return leftRes && rightRes;
	}
	
	/**
	 * 判断两棵二叉树是否相同的树(迭代)
	 * 遍历一遍即可,这里用preorder
	 */
	public static boolean isSame(TreeNode r1, TreeNode r2) {
		// 如果两个树都是空树,则返回true
		if(r1==null && r2==null){
			return true;
		}
		
		// 如果有一棵树是空树,另一颗不是,则返回false
		if(r1==null || r2==null){
			return false;
		}
		
		Stack<TreeNode> s1 = new Stack<TreeNode>();
		Stack<TreeNode> s2 = new Stack<TreeNode>();
		
		s1.push(r1);
		s2.push(r2);
		
		while(!s1.isEmpty() && !s2.isEmpty()){
			TreeNode n1 = s1.pop();
			TreeNode n2 = s2.pop();
			if(n1==null && n2==null){
				continue;
			}else if(n1!=null && n2!=null && n1.val==n2.val){
				s1.push(n1.right);
				s1.push(n1.left);
				s2.push(n2.right);
				s2.push(n2.left);
			}else{
				return false;
			}
		}
		return true;
	}

	/**
	 * 判断二叉树是不是平衡二叉树 递归解法: 
	 * (1)如果二叉树为空,返回真
	 * (2)如果二叉树不为空,如果左子树和右子树都是AVL树并且左子树和右子树高度相差不大于1,返回真,其他返回假
	 */
	public static boolean isAVLRec(TreeNode root) {
		if(root == null){			// 如果二叉树为空,返回真
			return true;
		}
		
		// 如果左子树和右子树高度相差大于1,则非平衡二叉树, getDepthRec()是前面实现过的求树高度的方法
		if(Math.abs(getDepthRec(root.left) - getDepthRec(root.right)) > 1){
			return false;
		}
		
		// 递归判断左子树和右子树是否为平衡二叉树
		return isAVLRec(root.left) && isAVLRec(root.right);
	}
	

	/**
	 * 求二叉树的镜像 递归解法: 
	 * (1)如果二叉树为空,返回空
	 * (2)如果二叉树不为空,求左子树和右子树的镜像,然后交换左子树和右子树
	 */
	// 1. 破坏原来的树,把原来的树改成其镜像
	public static TreeNode mirrorRec(TreeNode root) {
		if (root == null) {
			return null;
		}

		TreeNode left = mirrorRec(root.left);
		TreeNode right = mirrorRec(root.right);

		root.left = right;
		root.right = left;
		return root;
	}
	
	// 2. 不能破坏原来的树,返回一个新的镜像树
	public static TreeNode mirrorCopyRec(TreeNode root){
		if(root == null){
			return null;
		}
		
		TreeNode newNode = new TreeNode(root.val);
		newNode.left = mirrorCopyRec(root.right);
		newNode.right = mirrorCopyRec(root.left);
		
		return newNode;
	}
	
	// 3. 判断两个树是否互相镜像
	public static boolean isMirrorRec(TreeNode r1, TreeNode r2){
		// 如果两个树都是空树,则返回true
		if(r1==null && r2==null){
			return true;
		}
		
		// 如果有一棵树是空树,另一颗不是,则返回false
		if(r1==null || r2==null){
			return false;
		}
		
		// 如果两个树都非空树,则先比较根节点
		if(r1.val != r2.val){
			return false;
		}
		
		// 递归比较r1的左子树的镜像是不是r2右子树 和 
		// r1的右子树的镜像是不是r2左子树
		return isMirrorRec(r1.left, r2.right) && isMirrorRec(r1.right, r2.left);
	}
	
	// 1. 破坏原来的树,把原来的树改成其镜像
	public static void mirror(TreeNode root) {
		if(root == null){
			return;
		}
		
		Stack<TreeNode> stack = new Stack<TreeNode>();
		stack.push(root);
		while( !stack.isEmpty() ){
			TreeNode cur = stack.pop();
			
			// 交换左右孩子
			TreeNode tmp = cur.right;
			cur.right = cur.left;
			cur.left = tmp;
			
			if(cur.right != null){
				stack.push(cur.right);
			}
			if(cur.left != null){
				stack.push(cur.left);
			}
		}
	}
	
	// 2. 不能破坏原来的树,返回一个新的镜像树
	public static TreeNode mirrorCopy(TreeNode root){
		if(root == null){
			return null;
		}
		
		Stack<TreeNode> stack = new Stack<TreeNode>();
		Stack<TreeNode> newStack = new Stack<TreeNode>();
		stack.push(root);
		TreeNode newRoot = new TreeNode(root.val);
		newStack.push(newRoot);
		
		while( !stack.isEmpty() ){
			TreeNode cur = stack.pop();
			TreeNode newCur = newStack.pop();
			
			if(cur.right != null){
				stack.push(cur.right);
				newCur.left = new TreeNode(cur.right.val);
				newStack.push(newCur.left);
			}
			if(cur.left != null){
				stack.push(cur.left);
				newCur.right = new TreeNode(cur.left.val);
				newStack.push(newCur.right);
			}
		}
		
		return newRoot;
	}
	

	/**
	 * 求二叉树中两个节点的最低公共祖先节点 
	 * 递归解法: 
	 * (1)如果两个节点分别在根节点的左子树和右子树,则返回根节点
	 * (2)如果两个节点都在左子树,则递归处理左子树;如果两个节点都在右子树,则递归处理右子树
	 */
	public static TreeNode getLastCommonParentRec(TreeNode root, TreeNode n1, TreeNode n2) {
		if (findNodeRec(root.left, n1)) {				// 如果n1在树的左子树
			if (findNodeRec(root.right, n2)) { 		// 如果n2在树的右子树
				return root; 								// 返回根节点
			} else { 			// 如果n2也在树的左子树
				return getLastCommonParentRec(root.left, n1, n2); // 递归处理
			}
		} else { 				// 如果n1在树的右子树
			if (findNodeRec(root.left, n2)) { 			// 如果n2在左子树
				return root;
			} else {				 // 如果n2在右子树
				return getLastCommonParentRec(root.right, n1, n2); // 递归处理
			}
		}
	}

	// 帮助方法,递归判断一个点是否在树里
	private static boolean findNodeRec(TreeNode root, TreeNode node) {
		if (root == null || node == null) {
			return false;
		}
		if (root == node) {
			return true;
		}

		// 先尝试在左子树中查找
		boolean found = findNodeRec(root.left, node);
		if (!found) { 		// 如果查找不到,再在右子树中查找
			found = findNodeRec(root.right, node);
		}
		return found;
	}
	
	// 求二叉树中两个节点的最低公共祖先节点 (更加简洁版的递归)
	public static TreeNode getLastCommonParentRec2(TreeNode root, TreeNode n1, TreeNode n2) {
		if(root == null){
			return null;
		}
		
		// 如果有一个match,则说明当前node就是要找的最低公共祖先
		if(root.equals(n1) || root.equals(n2)){
			return root;
		}
		TreeNode commonInLeft = getLastCommonParentRec2(root.left, n1, n2);
		TreeNode commonInRight = getLastCommonParentRec2(root.right, n1, n2);
		
		// 如果一个左子树找到,一个在右子树找到,则说明root是唯一可能的最低公共祖先
		if(commonInLeft!=null && commonInRight!=null){
			return root;
		}
		
		// 其他情况是要不然在左子树要不然在右子树
		if(commonInLeft != null){
			return commonInLeft;
		}
		
		return commonInRight;
	}

	/**
	 * 非递归解法: 
	 * 先求从根节点到两个节点的路径,然后再比较对应路径的节点就行,最后一个相同的节点也就是他们在二叉树中的最低公共祖先节点
	 */
	public static TreeNode getLastCommonParent(TreeNode root, TreeNode n1, TreeNode n2) {
		if (root == null || n1 == null || n2 == null) {
			return null;
		}

		ArrayList<TreeNode> p1 = new ArrayList<TreeNode>();
		boolean res1 = getNodePath(root, n1, p1);
		ArrayList<TreeNode> p2 = new ArrayList<TreeNode>();
		boolean res2 = getNodePath(root, n2, p2);

		if (!res1 || !res2) {
			return null;
		}

		TreeNode last = null;
		Iterator<TreeNode> iter1 = p1.iterator();
		Iterator<TreeNode> iter2 = p2.iterator();

		while (iter1.hasNext() && iter2.hasNext()) {
			TreeNode tmp1 = iter1.next();
			TreeNode tmp2 = iter2.next();
			if (tmp1 == tmp2) {
				last = tmp1;
			} else { // 直到遇到非公共节点
				break;
			}
		}
		return last;
	}

	// 把从根节点到node路径上所有的点都添加到path中
	private static boolean getNodePath(TreeNode root, TreeNode node, ArrayList<TreeNode> path) {
		if (root == null) {
			return false;
		}
		
		path.add(root);		// 把这个节点加到路径中
		if (root == node) {
			return true;
		}

		boolean found = false;
		found = getNodePath(root.left, node, path); // 先在左子树中找
		
		if (!found) { 				// 如果没找到,再在右子树找
			found = getNodePath(root.right, node, path);
		}
		if (!found) { 				// 如果实在没找到证明这个节点不在路径中,说明刚才添加进去的不是路径上的节点,删掉!
			path.remove(root);	
		}

		return found;
	}

	/**
	 * 求二叉树中节点的最大距离 即二叉树中相距最远的两个节点之间的距离。 (distance / diameter)
	 * 递归解法: 
	 * (1)如果二叉树为空,返回0,同时记录左子树和右子树的深度,都为0
	 * (2)如果二叉树不为空,最大距离要么是左子树中的最大距离,要么是右子树中的最大距离,
	 * 要么是左子树节点中到根节点的最大距离+右子树节点中到根节点的最大距离,
	 * 同时记录左子树和右子树节点中到根节点的最大距离。
	 * 
	 * http://www.cnblogs.com/miloyip/archive/2010/02/25/1673114.html
	 * 
	 * 计算一个二叉树的最大距离有两个情况:

		情况A: 路径经过左子树的最深节点,通过根节点,再到右子树的最深节点。
		情况B: 路径不穿过根节点,而是左子树或右子树的最大距离路径,取其大者。
		只需要计算这两个情况的路径距离,并取其大者,就是该二叉树的最大距离
	 */
	public static Result getMaxDistanceRec(TreeNode root){
		if(root == null){
			Result empty = new Result(0, -1);		// 目的是让调用方 +1 后,把当前的不存在的 (NULL) 子树当成最大深度为 0
			return empty;
		}
		
		// 计算出左右子树分别最大距离
		Result lmd = getMaxDistanceRec(root.left);
		Result rmd = getMaxDistanceRec(root.right);
		
		Result res = new Result();
		res.maxDepth = Math.max(lmd.maxDepth, rmd.maxDepth) + 1;		// 当前最大深度
		// 取情况A和情况B中较大值
		res.maxDistance = Math.max( lmd.maxDepth+rmd.maxDepth, Math.max(lmd.maxDistance, rmd.maxDistance) );
		return res;
	}
	
	private static class Result{
		int maxDistance;
		int maxDepth;
		public Result() {
		}

		public Result(int maxDistance, int maxDepth) {
			this.maxDistance = maxDistance;
			this.maxDepth = maxDepth;
		}
	}
	
	/**
	 * 13. 由前序遍历序列和中序遍历序列重建二叉树(递归)
	 * 感觉这篇是讲的最为清晰的:
	 * http://crackinterviewtoday.wordpress.com/2010/03/15/rebuild-a-binary-tree-from-inorder-and-preorder-traversals/
	 * 文中还提到一种避免开额外空间的方法,等下次补上
	 */
	public static TreeNode rebuildBinaryTreeRec(List<Integer> preOrder, List<Integer> inOrder){
		TreeNode root = null;
        List<Integer> leftPreOrder;
        List<Integer> rightPreOrder;
        List<Integer> leftInorder;
        List<Integer> rightInorder;
        int inorderPos;
        int preorderPos;
 
        if ((preOrder.size() != 0) && (inOrder.size() != 0))
        {
        	// 把preorder的第一个元素作为root
            root = new TreeNode(preOrder.get(0));
 
            //  Based upon the current node data seperate the traversals into leftPreorder, rightPreorder,
            //  leftInorder, rightInorder lists
            // 因为知道root节点了,所以根据root节点位置,把preorder,inorder分别划分为 root左侧 和 右侧 的两个子区间
            inorderPos = inOrder.indexOf(preOrder.get(0));		// inorder序列的分割点
            leftInorder = inOrder.subList(0, inorderPos);
            rightInorder = inOrder.subList(inorderPos + 1, inOrder.size());
 
            preorderPos = leftInorder.size();							// preorder序列的分割点
            leftPreOrder = preOrder.subList(1, preorderPos + 1);
            rightPreOrder = preOrder.subList(preorderPos + 1, preOrder.size());
 
            root.left = rebuildBinaryTreeRec(leftPreOrder, leftInorder);		// root的左子树就是preorder和inorder的左侧区间而形成的树
            root.right = rebuildBinaryTreeRec(rightPreOrder, rightInorder);	// root的右子树就是preorder和inorder的右侧区间而形成的树
        }
 
        return root;
	}
	
	/**
	 	14.  判断二叉树是不是完全二叉树(迭代)
	  	若设二叉树的深度为h,除第 h 层外,其它各层 (1~h-1) 的结点数都达到最大个数,
	  	第 h 层所有的结点都连续集中在最左边,这就是完全二叉树。
		有如下算法,按层次(从上到下,从左到右)遍历二叉树,当遇到一个节点的左子树为空时,
		则该节点右子树必须为空,且后面遍历的节点左右子树都必须为空,否则不是完全二叉树。
	 */
	public static boolean isCompleteBinaryTree(TreeNode root){
		if(root == null){
			return false;
		}
		
		Queue<TreeNode> queue = new LinkedList<TreeNode>();
		queue.add(root);
		boolean mustHaveNoChild = false;
		boolean result = true;
		
		while( !queue.isEmpty() ){
			TreeNode cur = queue.remove();
			if(mustHaveNoChild){	// 已经出现了有空子树的节点了,后面出现的必须为叶节点(左右子树都为空)  
				if(cur.left!=null || cur.right!=null){
					result = false;
					break;
				}
			} else {
				if(cur.left!=null && cur.right!=null){		// 如果左子树和右子树都非空,则继续遍历
					queue.add(cur.left);
					queue.add(cur.right);
				}else if(cur.left!=null && cur.right==null){	// 如果左子树非空但右子树为空,说明已经出现空节点,之后必须都为空子树
					mustHaveNoChild = true;
					queue.add(cur.left);
				}else if(cur.left==null && cur.right!=null){	// 如果左子树为空但右子树非空,说明这棵树已经不是完全二叉完全树!
					result = false;
					break;
				}else{			// 如果左右子树都为空,则后面的必须也都为空子树
					mustHaveNoChild = true;
				}
			}
		}
		return result;
	}
	
	/**
	 * 14.  判断二叉树是不是完全二叉树(递归)
	 * http://stackoverflow.com/questions/1442674/how-to-determine-whether-a-binary-tree-is-complete
	 * 
	 */
	public static boolean isCompleteBinaryTreeRec(TreeNode root){
//		Pair notComplete = new Pair(-1, false);
//		return !isCompleteBinaryTreeSubRec(root).equalsTo(notComplete);
		return isCompleteBinaryTreeSubRec(root).height != -1;
	}
	
	// 递归判断是否满树(完美)
	public static boolean isPerfectBinaryTreeRec(TreeNode root){
		return isCompleteBinaryTreeSubRec(root).isFull;
	}
	
	// 递归,要创建一个Pair class来保存树的高度和是否已满的信息
	public static Pair isCompleteBinaryTreeSubRec(TreeNode root){
		if(root == null){
			return new Pair(0, true);
		}
		
		Pair left = isCompleteBinaryTreeSubRec(root.left);
		Pair right = isCompleteBinaryTreeSubRec(root.right);
		
		// 左树满节点,而且左右树相同高度,则是唯一可能形成满树(若右树也是满节点)的情况
		if(left.isFull && left.height==right.height){
			return new Pair(1+left.height, right.isFull);
		}
		
		// 左树非满,但右树是满节点,且左树高度比右树高一
		// 注意到如果其左树为非完全树,则它的高度已经被设置成-1,
		// 因此不可能满足第二个条件!
		if(right.isFull && left.height==right.height+1){
			return new Pair(1+left.height, false);
		}
		
		// 其他情况都是非完全树,直接设置高度为-1
		return new Pair(-1, false);
	}
	
	private static class Pair{
		int height;				// 树的高度
		boolean isFull;		// 是否是个满树

		public Pair(int height, boolean isFull) {
			this.height = height;
			this.isFull = isFull;
		}

		public boolean equalsTo(Pair obj){
			return this.height==obj.height && this.isFull==obj.isFull;
		}
	}
	
}
</span>

二叉树面试题(高频) 目录 1. 检查两颗树是否相同 1.1题目描述 1.2思考过程 1.3代码 2. 另一颗树的子树 2.1题目描述 2.2思考过程 2.3代码 3. 二叉树最大深度 3.1题目描述 3.2思考过程 3.3代码 4. 判断一颗二叉树是否是平衡二叉树 4.1题目描述 4.2思考过程 4.3代码 5. 对称二叉树 5.1题目描述 5.2思考过程 5.3代码 6.二叉树的分层遍历 6.1题目描述 6.2思考过程 6.3代码 7.二叉树的构建及遍历 7.1题目... 阅读详情

相关推荐

告别以太网瓶颈:手把手教你为AI训练集群搭建InfiniBand网络(以NVIDIA DGX A100/H100为例)

本文详细指导如何为AI训练集群搭建InfiniBand网络,解决以太网瓶颈问题,特别针对NVIDIA DGX A100/H100系统。通过硬件选型、网络架构设计和性能调优,显著提升多机多卡训练效率,适用于大规模模型训练场景。

weixin_30336061的博客 311

十道二叉树面试题,对二叉树理解更进一步

学习二叉树有一个很重要的思想就是分治(将一个大问题划分为最小规模的子问题),常用方法之一就是递归,而递归是很抽象的,当对递归过程不理解时,建议多画图将递归过程实例化,拒绝人脑压栈...

weixin_46016019的博客 3460

通义千问对接FreeSWITCH实现大模型呼叫中心

通义千问是由阿里云开发的大规模语言模型,具备优秀的自然语言处理能力,支持文本生成、问答对话、摘要提取等多种任务。它不仅拥有丰富的知识储备,还能根据上下文灵活调整回答策略,适用于各种复杂场景下的自动化交流。FreeSWITCH是一个开源的多媒体通信服务器,提供了包括VoIP通话、即时消息传递在内的多种通信服务。它的模块化设计允许开发者根据实际需求定制功能,广泛应用于企业级通信系统中。通过集成通义千问,FreeSWITCH能够实现更智能的客户服务体验。

技术永不眠 Q:53195017 1499

二叉树精选面试题

记录了自己在学习数据结构的过程中刷的题,一棵树的子树,翻转二叉树,平衡二叉树,对称二叉树,平衡二叉树,最近公共祖先等等

2202_76097976的博客 2108

告别高延迟!用RV1106+4G模块+libdatachannel搭建低功耗WebRTC监控,保姆级避坑指南

本文详细介绍了如何利用RV1106低功耗处理器、4G模块和libdatachannel库构建嵌入式WebRTC监控系统,解决高延迟问题。通过硬件选型、系统优化和核心代码实现,提供了一套完整的低功耗实时视频传输方案,特别适合智能安防和工业物联网应用。

weixin_42527401的博客 193

Java使用easyExcel生成excel文件直接写入邮件附件并发送

1.引入pom依赖 <!-- email --> <dependency> <groupId>javax.mail</groupId> <artifactId>mail</artifactId> <version>1.4.7</version> </dependency> <!-- easyexcel需要依赖poi jar包 --> <depe

weixin_50493320的博客 3910

【数据结构】二叉树面试题总结(持续更新中...)

总结了二叉树的相关习题。

weixin_44258092的博客 1733

五大经典遥感数据集对比:LoveDA、Potsdam、Toronto、Vaihingen、Massachusetts Roads选哪个?

本文深度对比了LoveDA、Potsdam、Vaihingen、Toronto和Massachusetts Roads五大经典遥感数据集。针对城市监测、道路提取、土地分类等不同任务,从分辨率、标注质量、数据规模及场景多样性等核心维度进行分析,为研究者根据项目目标、硬件条件与应用场景选择最合适的遥感数据集提供决策指南。

weixin_29057065的博客 237

一文搞定面试中的二叉树题目(Java实现)

一篇文章搞定面试中的二叉树题目(java实现) 最近总结了二叉树的题目。 先上二叉树的数据结构: class TreeNode{ int val; //左孩子 TreeNode left; //右孩子 TreeNode right; } 二叉树的题目普遍可以用递归和迭代的方式来解 1.求二叉树的最大深度 int maxDeath(TreeNode node...

付石头的博客 2881

【超详细】数据分析笔试题分享,可以收藏后仔细阅读_数据分析岗位笔试题目

这种做法是最精确的做法,保留了所有的信息,也未添加任何额外信息,若预处理时把所有的变量都这样处理,会大大增加数据的维度。15、在某座城市出现了某种流行病,且已知某座城市男女人数相同,男性患该病的概率为5%,女性为0.25%,现已知该城市的小明得了此病,请问小明为男性的概率是() B。D项,使用方差作为特征评分标准,如果某个特征的取值差异不大,通常认为该特征对区分样本的贡献度不大,因此在构造特征过程中去掉方差小于阈值的特征。LTV,用户生命周期价值,是产品从用户获取到流失所得到的全部收益的总和。

2401_89824744的博客 950

风电光伏功率预测竞赛精华总结:技术解析与实战经验

本文深度解析新能源功率预测竞赛的技术要点与实战经验。首先介绍了风电、光伏功率预测对电网调度优化、降低运营风险的重要性,以及多个竞赛的基本概况。在数据特征分析部分,详细探讨了气象预报数据和功率数据的特征,包括时间周期性、数据质量问题及处理方法。针对异常值检测、数据标准化和特征工程(如时间特征、统计聚合特征、风速风向特征和滞后特征)提供了具体的技术实现方案。文章为新能源功率预测提供了实用的技术指导,对相关竞赛和实际应用具有重要参考价值。

算法工程师 206

Java面试:重建二叉树题目

题目:输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。 解答: public static void main(String[] args) { //前序遍历数 int [] preNode ={1,2,4,7,3,5,6,8}; //中序遍历数 int [...

钟渊博客 216

Open3D 体素下采样【2026最新版】

体素滤波,博客长期更新,本文最近更新时间为:2026年4月11日。

点云侠的博客 37万+

Java搞定面试中的二叉树题目

这是本系列的第二篇,与前一篇 面试大总结之一:Java搞定面试中的链表题目 相比,二叉树的题目可以变化的就更多了。本文还是参考整合重写了《轻松搞定面试中的二叉树题目》和《算法大全(3) 二叉树》两篇大作。本文一个小亮点就是几乎每一道题都用了递归和迭代两种方法写过一遍,因为面试时往往可能会被要求写不擅长的那一种。这一千多行的记录也是我在面试摸索过程中的一个小笔记,备份与此。请大神们轻拍指正。今后

编程爱好者 1259

汉书HE8,好用的汇编代码编写工具

汉书HE8,好用的汇编代码编写工具早期编写汇编代码所用功能强大

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

二叉树,2021年最新大厂Java面试笔试题目

从根开始定义起,根为第一层,根的孩子为第二层,以此类推。 树的深度 树中结点的最大层次数称为树的深度或高度。 如图树的深度为4。 二叉树 定义 二叉树是n(n>=0)个结点的有限集合,该集合或者为空集(称为空二叉树),或者由一个根结点和两棵互不相交的、分别称为根结点的左子树和右子树组成。 图解 二叉树特点 由二叉树定义以及图示分析得出二叉树有以下特点: 每个结点最多有两颗子树,所以二叉树中不存在度大于2的结点。 左子树和右子树是有顺序的,次序不能任意颠倒。 即使树中某结点只有一棵子树,也要区分.

m0_60606523的博客 269

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

面试大总结:Java搞定面试中的二叉树题目

转载自:http://blog.youkuaiyun.com/fightforyourdream/article/details/16843303 这是本系列的第二篇,与前一篇 面试大总结之一:Java搞定面试中的链表题目 相比,二叉树的题目可以变化的就更多了。本文还是参考整合重写了《轻松搞定面试中的二叉树题目》和《算法大全(3) 二叉树》两篇大作。本文一个小亮点就是几乎每一道题都用了递归和迭代两种方法

帅性而为1号的博客 532

老版QQ原生表情gif动态

这是老版的QQ表情,不过全是动态的的,Windows照片查看器是不会动的,可以右击用浏览器打开查看,再说一遍是老版的,只是分享给宁愿用老版也要动态的人。

面试中的二叉树题目,java描述

本文转载自http://blog.youkuaiyun.com/fightforyourdream/article/details/16843303**题目: 1. 前序、中序、后序遍历二叉树 2. 层序遍历二叉树 3. 获得二叉树的深度 4. 获得二叉树的节点个数 5. 判断两棵二叉树是否为相同的二叉树 6. 判断二叉树是否为平衡二叉树 7. 获得二叉树的叶子节点个数 8. 获

小柒木有熙 1788

java面试中的二叉树题目汇总

二叉树和链表一样,首先都应该想到递归。所以本文中尽量都用递归和非递归完成每一题1.二叉树的遍历,前序中序后序,递归和非递归2.二叉树的层序遍历3.二叉树的高度4.二叉树的节点个数5.求二叉树的镜像6.判断两颗二叉树是否互为镜像7.判断一棵树是否本身就是镜像树8.判断两颗二叉树是不是相同的树9.判断树1是不是树2的子结构10.判断二叉树是否是平衡二叉树11.二叉树第k层的节点个数12.二叉树叶子节点的

myblog 916

java 树 面试题_面试大总结之二:Java搞定面试中的二叉树题目

这是本系列的第二篇,与前一篇面试大总结之一:Java搞定面试中的链表题目相比,二叉树的题目可以变化的就更多了。本文还是参考整合重写了《轻松搞定面试中的二叉树题目》和《算法大全(3)二叉树》两篇大作。本文一个小亮点就是几乎每一道题都用了递归和迭代两种方法写过一遍,因为面试时往往可能会被要求写不擅长的那一种。这一千多行的记录也是我在面试摸索过程中的一个小笔记,备份与此。请大神们轻拍指正。今后我也会...

weixin_33096511的博客 257

java中的树面试题_java面试中的二叉树题目汇总

二叉树和链表一样,首先都应该想到递归。所以本文中尽量都用递归和非递归完成每一题1.二叉树的遍历,前序中序后序,递归和非递归2.二叉树的层序遍历3.二叉树的高度4.二叉树的节点个数5.求二叉树的镜像6.判断两颗二叉树是否互为镜像7.判断一棵树是否本身就是镜像树8.判断两颗二叉树是不是相同的树9.判断树1是不是树2的子结构10.判断二叉树是否是平衡二叉树11.二叉树第k层的节点个数12.二叉树叶子节点...

weixin_28727975的博客 443

搞定面试中的二叉树题目(Java实现)

本文转载自:https://www.jianshu.com/p/0190985635eb这是一篇关于二叉树的文章,总结了二叉树数据结构和算法的相关题目。先上二叉树的数据结构:class TreeNode{ int val; //左孩子 TreeNode left; //右孩子 TreeNode right; } 二叉树的题目普遍可以用递归和迭代的方式来解1....

样young的博客 4287
上一篇: 递归算法总结java版
下一篇: 去石子游戏之博弈与最优策略
0802小乌龟
博客等级 码龄17年 2粉丝 25原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

抵扣说明:

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

余额充值