面试题28 对称的二叉树
请实现一个函数,用来判断一颗二叉树是不是对称的。如果一棵二叉树和它的镜像一样,那么它是对称的。
测试用例
- 功能测试(对称的树;因结构而不对称的二叉树;结构对称但节点值不对称的二叉树)
- 特殊值测试(树为null;树只有一个根节点;所有节点值都相同的树)
实现代码
/**
* 入口方法,负责进行参数校验和递归调用
* @param root 树
* @return 是否对称
*/
public static boolean isSymmetrical(BinaryTreeNode root) {
return root == null || isSymmetrical(root, root);
}
/**
* 核心递归方法,进行前序遍历和对称前序遍历对应位置的比较
* @param tree1 root树
* @param tree2 root树
* @return true 对称 or false 不对称
*/
private static boolean isSymmetrical(BinaryTreeNode tree1, BinaryTreeNode tree2){
if (tree1 == null && tree2 == null)
return true;
if (tree1 == null || tree2 == null)
return false;
if (tree1.nValue != tree2.nValue)
return false;
return isSymmetrical(tree1.left,tree2.right) && isSymmetrical(tree1.right,tree2.left);
}
算法思路
树的3种遍历方法均是先遍历左孩子,再遍历右孩子。以前序遍历为例,考虑一种新的对称的前序遍历方法,先遍历右孩子,再遍历左孩子。如果两棵树对称,那么前序遍历和对称前序遍历对应位置上的节点值相同(包括null)。使用递归可以很简洁地实现。
面试题29 顺时针打印矩阵
输入一个矩阵,按照从外向里以顺时针的顺序依次打印出每一个数字。
测试用例
- 功能测试(正常矩阵;矩阵只有一个元素;矩阵只有行或列)
- 特殊值测试(矩阵为null)
实现代码
/**
* 打印矩阵的接口方法,是递归方法的入口
* @param matrix 要打印的矩阵
*/
public void printMatrix(int[][] matrix){
if (matrix == null)
return;
int row = 0,col = 0;
int rows = matrix.length,cols = matrix[0].length;
printCore(matrix,row,col,rows,cols);
}
/**
* 打印矩阵的核心递归方法
* @param matrix 矩阵
* @param row 打印起始的起始行
* @param col 打印起始的起始列
* @param rows 还没打印的行数
* @param cols 还没打印的列数
*/
private void printCore(int[][] matrix, int row, int col, int rows, int cols) {
if (rows < 1 || cols<1)
return;
//第一步,打印一行
for (int i =0;i<cols;i++)
System.out.print(matrix[row][col + i] + " ");
//第二步,需要条件rows>1
if (rows>1){
for (int j =1;j<rows;j++)
System.out.print(matrix[row+j][col+cols-1]+" ");
}
//第三步,需要条件中间的矩阵至少要有两行两列
if (rows>=2 && cols>=2){
for (int m =1;m<cols;m++)
System.out.print(matrix[row+rows-1][col+cols-1-m]+" ");
}
//第四步,至少有3行2列
if (rows>=3 && cols>=2){
for (int n=1;n<=rows-2;n++)
System.out.print(matrix[row+rows-1-n][col]+" ");
}
printCore(matrix,row+1,col+1,rows-2,cols-2);
}
//测试方法
public static void main(String[] args) {
InterQuestions29 test = new InterQuestions29();
int[][] matrix0 = {{1,2,3,4},{5,6,7,8},{9,10,11,12},{13,14,15,16}};
int[][] matrix1 = {{1,2,3,4},{5,6,7,8}};
int[][] matrix2 = {{1}};
int[][] matrix3 = {{}};
int[][] matrix4 = null;
System.out.println("mateix0:");
test.printMatrix(matrix0);
System.out.println("mateix1:");
test.printMatrix(matrix1);
System.out.println("mateix2:");
test.printMatrix(matrix2);
System.out.println("mateix3:");
test.printMatrix(matrix3);
System.out.println("mateix4:");
test.printMatrix(matrix4);
}
算法思路
首先打印最外围的一圈,记录起始坐标,未打印的行数、列数,递归进行。每一次打印分为四步。第一步,打印第一行,这一步总是必须的;第二步,打印最后一列,只有当列数大于1时才进行;第三步,打印最后一行,只有当前矩阵至少有两行两列时才进行(两行一列的情形已经在第二部处理了);第四部,打印第一列,只有当前矩阵至少有3行2列才进行。
面试题30 包含min函数的栈
定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的min函数。在该栈中,调用min、push及pop的时间复杂度都是O(1)。
测试样例
- 功能测试(栈为空时插入;新压入的数字比之前的最小值大或者小;弹出栈的不是最小元素;弹出栈的是最小元素)
- 特殊测试(栈为空时弹出)
实现代码
/**
* 包含min函数的栈
*/
public class InterQuestions30<AnyType extends Comparable<? super AnyType>> {
private Stack<AnyType> stack = new Stack<>();
private Stack<AnyType> assistStack = new Stack<>();
public void push(AnyType data){
if (stack.isEmpty() || data.compareTo(assistStack.peek())<0) {
stack.push(data);
assistStack.push(data);
} else {
stack.push(data);
assistStack.push(assistStack.peek());
}
}
public AnyType pop(){
if (!stack.empty()) {
assistStack.pop();
return stack.pop();
}
return null;
}
public AnyType min(){
return assistStack.peek();
}
public static void main(String[] args) {
InterQuestions30<Integer> myStack = new InterQuestions30<>();
myStack.push(3);
myStack.push(4);
myStack.push(2);
myStack.push(1);
System.out.println(myStack.pop());
System.out.println(myStack.pop());
myStack.push(0);
}
}
算法思路
使用一个辅助栈记录最小值。当压入的值小于辅助栈的栈顶时,将该元素压入辅助栈。当压入的值大于辅助栈的栈顶是,将辅助栈的栈顶复制一份,压入辅助栈。主弹出时,辅助栈也要同步弹出,辅助栈弹出的就是当前栈中的最小值。
面试题31 栈的压入、弹出序列
输入两个整数序列,第一个序列表示栈的压入顺序,请判断第二个序列是否为该栈的弹出顺序。假设压入栈的所有数字均不相等。例如,序列{1,2,3,4,5}是某栈的压栈序列,序列{4,5,3,2,1}是该压栈序列对应的一个弹出序列,但{4,3,5,1,2}就不可能是该压栈序列的弹出序列。
测试样例
- 功能测试(是或者不是弹出序列)
- 特殊值测试(两个序列位数不等;一个或者连个数列为空)
代码实现
public boolean isOutSeqOfStack(AnyType[] in,AnyType[] out) {
if (in.length != out.length)
return false;
Stack<AnyType> assistStack = new Stack<>();
int inIndex = 0, outIndex = 0;
int length = in.length;
while (in[inIndex].compareTo(out[outIndex]) == 0) {
inIndex++;
outIndex++;
if (inIndex == length)
return true;
}
assistStack.push(in[inIndex++]);
assistStack.push(in[inIndex++]);
while (true) {
if (outIndex == length)
return true;
if (assistStack.isEmpty()){
if (inIndex >length-1)
return false;
else
assistStack.push(in[inIndex++]);
}
if (assistStack.peek().compareTo(out[outIndex]) == 0) {
assistStack.pop();
outIndex++;
}else {
assistStack.push(in[inIndex++]);
}
}
}
public static void main(String[] args) {
Integer[] in={1,2,3};
Integer[] out1={3,1,2};
Integer[] out2={1,2,3};
Integer[] out3={1,3,2};
Integer[] out4={2,3,1};
Integer[] out5={2,1,3};
Integer[] out6={3,2,1};
InterQuestions31<Integer> test = new InterQuestions31<>();
System.out.println(test.isOutSeqOfStack(in,out1));
//System.out.println(test.isOutSeqOfStack(in,out2));
}
算法思路
建立一个辅助栈,一次进行判断。
面试题32 从上到下打印二叉树
不分行从上到下打印二叉树。丛上到下打印二叉树的么个节点,同一层的节点按照从左到右的顺序打印。二叉树节点的定义如下:
class BinaryTreeNode{
int tValue;
BinaryTreeNode left;
BinaryTreeNode right;
}
测试用例
- 功能测试(完全二叉树;所有节点只有左子树的二叉树;所有节点只有右子树的二叉树)
- 特殊输入测试(二叉树根节点为null;只有一个节点的二叉树)
实现代码
/**
* 使用队列按层打印二叉树
* @param tree 二叉树
*/
public void printBinaryNodeTreeByLevel(BinaryTreeNode tree){
Queue<BinaryTreeNode> queue = new LinkedBlockingDeque<>();
if (tree == null)
return;
System.out.print(tree.nValue+" ");
if (tree.left != null)
queue.add(tree.left);
if (tree.right != null)
queue.add(tree.right);
BinaryTreeNode head;
while (queue.size()>0){
head=queue.poll();
System.out.print(head.nValue+" ");
if (head.left != null)
queue.add(head.left);
if (head.right != null)
queue.add(head.right);
}
}
public static void main(String[] args) {
BinaryTreeNode node1 = new BinaryTreeNode(8);
BinaryTreeNode node2 = new BinaryTreeNode(6);
BinaryTreeNode node3 = new BinaryTreeNode(10);
BinaryTreeNode node4 = new BinaryTreeNode(5);
BinaryTreeNode node5 = new BinaryTreeNode(7);
BinaryTreeNode node6 = new BinaryTreeNode(9);
BinaryTreeNode node7 = new BinaryTreeNode(11);
node1.left=node2;
node1.right = node3;
node2.left=node4;
node2.right=node5;
node3.left = node6;
node3.right = node7;
/*//只有左子树
node1.left = node2;
node2.left = node4;*/
InterQuestions32 test = new InterQuestions32();
test.printBinaryNodeTreeByLevel(node1);
}
算法思路
使用一个辅助队列。先将头节点推入队列中。每次从队列弹出一个节点打印,如果该节点有子节点,则按先左后右的顺序推入队列中。重复前面的操作,直至队列中所有的节点都被打印出来。
面试题33 二叉搜索树的后序遍历序列
输入一个整数数组,判断该数组是不是某二叉搜索树的后序遍历结果。如果是则返回true,否则返回false。假设输入的数组的任意两个数字都互不相同。例如,输入数组{5,7,6,9,11,10,8},则返回true,因为这个整数序列是一颗二叉搜索树的后序遍历结果。如果输入的数组是{7,4,6,5},则由于没有哪颗二叉搜索树的后序遍历结果是这个序列,因此返回false。
测试用例
- 功能测试(完全二叉搜索树;所有节点都没有左/右子树的二叉树、只有一个节点的二叉树;输入的后续遍历序列没有对应的二叉搜索树)。
- 特殊输入测试(输入的后续遍历序列为null)。
实现代码
/**
* 验证序列是否为二叉搜索树的后续遍历
* @param seq 要验证的序列
* @param start 序列的起始位置
* @param end 序列的结束位置
* @return 是或否
*/
public boolean verifySeqOfBST(int[] seq,int start,int end){
//参数校验
if (seq == null || seq.length == 0)
return false;
//成功出口,针对只有一个或两个节点的序列
if (end == start || end-start == 1)
return true;
int root = seq[end];
//找出右子树的第一个节点
int i=start;
for (;i<end;i++){
if (seq[i]>root)
break;
}
//验证右子树是否大于根节点
int j=i;
for (;j<end;j++){
if (seq[j]<root)
return false;
}
//左子序列递归验证
boolean left = false;
if (i>start)
left = verifySeqOfBST(seq,start,i-1);
//右子序列递归验证
boolean right = false;
if (i<end)
right = verifySeqOfBST(seq,i,end-1);
return left && right;
}
public static void main(String[] args) {
InterQuestions33 test = new InterQuestions33();
int[] a = {5,7,6,9,11,10,8};
int[] b= {7,4,6,5};
System.out.println(test.verifySeqOfBST(a,0,a.length-1));
System.out.println(test.verifySeqOfBST(b,0,b.length-1));
}
算法思想
在后序遍历的序列中,最后一个数字是树的根节点的值。数组中前面的数字可以分为两部分:第一部分是左子树节点的值,它们都比根节点小。第二部分是右子树节点的值,它们都比根节点的值大。
面试题34 二叉树中和为某一值的路径
输入一颗二叉树和一个整数,打印出二叉树中节点值的和为输入整数的所有路径。从树的根节点开始往下一直到叶节点所经过的节点形成一条路径。二叉树定义如下:
class BinaryTreeNode{
int tValue;
BinaryTreeNode left;
BinaryTreeNode right;
}
测试样例
- 功能测试(树中包含0条、1条或多条符合的路径)
- 特殊值测试(树为空;整数为空)
实现代码
/**
* 求路径的核心方法
* @param treeNode 二叉树
* @param num 特定整数
* @param sum 当前的和
* @param stack 存放之前路径的栈
*/
public void getSpecifiedPath(BinaryTreeNodeFor34 treeNode, int num, int sum, Stack<BinaryTreeNodeFor34> stack) {
stack.push(treeNode);
sum += treeNode.nValue;
if (treeNode.left == null && treeNode.right == null && sum == num) {
//使用foreach对栈进行遍历,方向是从栈底到栈顶
for (BinaryTreeNodeFor34 aStack : stack)
System.out.print(aStack.nValue + "-->");
System.out.println();
}
if (treeNode.left != null)
getSpecifiedPath(treeNode.left, num, sum , stack);
if (treeNode.right != null)
getSpecifiedPath(treeNode.right,num,sum,stack);
stack.pop();
}
public static void main(String[] args) {
BinaryTreeNodeFor34 node1 = new BinaryTreeNodeFor34(10);
BinaryTreeNodeFor34 node2 = new BinaryTreeNodeFor34(5);
BinaryTreeNodeFor34 node3 = new BinaryTreeNodeFor34(12);
BinaryTreeNodeFor34 node4 = new BinaryTreeNodeFor34(4);
BinaryTreeNodeFor34 node5 = new BinaryTreeNodeFor34(7);
node1.left=node2;
node1.right=node3;
node2.left=node4;
node2.right=node5;
InterQuestions34 test = new InterQuestions34();
test.getSpecifiedPath(node1,22,0,new Stack<>());
}
/* 测试结果
10-->5-->7-->
10-->12-->
*/
算法思路
二叉树的先序遍历正好是一条路径相连。使用栈存储已经遍历的路径,当遇到叶节点时,如果栈中所有元素和等于指定的整数,则打印该路径,然后返回上一层,继续遍历其他路径。
面试题35 复杂链表的复制
请实现一个函数,复制一个复杂链表。在复杂链表中,每个节点除了有一个next指针指向下一个节点,还有一个sibling指针指向链表中的任意节点或者null。节点的定义如下:
class ComplexListNode{
int nValue;
ComplexListNode nextNode;
ComplexListNode siblingNode;
}
测试样例
- 功能测试(节点中的sibling指向节点自身;两个节点的sibling形成环状;链表只有一个节点)。
- 特殊输入测试(指向链表头节点的指针为null)
实现代码
/**
* 主方法,负责方法调用和参数校验
* @param head 复杂链表的头节点
* @return 复制链表
*/
public ComplexListNode clone(ComplexListNode head){
if (head == null)
return null;
cloneNodes(head);// 复制原节点
connectSiblings(head); //连接兄弟节点
return reConnectNodes(head); //拆分并返回复制的复杂链表
}
/**
* 克隆节点,并将节点连接到原节点之后
* @param head 原链表的头节点
*/
private void cloneNodes(ComplexListNode head){
ComplexListNode current = head;
while (current!=null){
ComplexListNode cloneNode = new ComplexListNode();
cloneNode.nValue = current.nValue;
cloneNode.nextNode = current.nextNode;
cloneNode.nextNode = current.nextNode;
current.nextNode = cloneNode;
current = cloneNode.nextNode;
}
}
/**
* 复制兄弟链
* @param head 未复制兄弟链的链表
*/
private void connectSiblings(ComplexListNode head) {
ComplexListNode current = head;
while (current != null){
ComplexListNode cloneNodeOfCurrent = current.nextNode;
//连接克隆节点的兄弟节点
if (current.siblingNode !=null )
cloneNodeOfCurrent.siblingNode = current.siblingNode.nextNode;
current = cloneNodeOfCurrent.nextNode;
}
}
/**
* 拆分两个链表的方法
* @param head 复制后,但连在一起的链表头节点
* @return 复制的链表
*/
private ComplexListNode reConnectNodes(ComplexListNode head) {
ComplexListNode node = head;
ComplexListNode cloneHead = null;
ComplexListNode cloneNode = null;
if (node != null){
cloneHead=cloneNode=node.nextNode;
node.nextNode=cloneNode.nextNode;
node=node.nextNode;
}
while (node != null){
cloneNode.nextNode=node.nextNode;
cloneNode = cloneNode.nextNode;
node.nextNode = cloneNode.nextNode;
node = node.nextNode;
}
return cloneHead;
}
public static void main(String[] args) {
InterQuestions35 test = new InterQuestions35();
ComplexListNode node1 = new ComplexListNode();
node1.nValue = 1;
ComplexListNode node2 = new ComplexListNode();
node2.nValue = 2;
ComplexListNode node3 = new ComplexListNode();
node3.nValue = 3;
ComplexListNode node4 = new ComplexListNode();
node4.nValue = 4;
ComplexListNode node5 = new ComplexListNode();
node5.nValue = 5;
node1.nextNode = node2;
node1.siblingNode = node3;
node2.nextNode = node3;
node2.siblingNode = node5;
node3.nextNode = node4;
node3.siblingNode = null;
node4.nextNode = node5;
node4.siblingNode = node2;
ComplexListNode clone = test.clone(node1);
while (clone!=null){
System.out.print("node:"+clone.nValue);
if (clone.nextNode!=null)
System.out.print("next:"+clone.nextNode.nValue);
if (clone.siblingNode!=null)
System.out.print(":sibling"+clone.siblingNode.nValue);
clone = clone.nextNode;
System.out.println();
}
}
/* 测试结果
node:1next:2:sibling3
node:2next:3:sibling5
node:3next:4
node:4next:5:sibling2
node:5
*/
算法思路
总共可以分为3步。第一步,复制每一个节点,并将复制节点连接到该节点后;第二部,复制每一个节点的兄弟链siblingNode;第三步,拆分为两个链表,并返回复制的链表。
面试题36 二叉搜索树与双向链表
输入一颗二叉搜索树,将该二叉搜索树转换成一个排序的双向链表。要求不能创建任何新的节点,只能调整树中节点指针的指向。二叉树的节点定义如下:
class BinaryTreeNode{
int tValue;
BinaryTreeNode left;
BinaryTreeNode right;
}
测试样例
- 功能测试(二叉树有多个节点;二叉树只有一个节点;二叉树所有节点只有左子树或右子树)
- 特殊测试(树的根节点为null)
实现代码
/**
* 主方法,负责递归方法ConvertNode的调用
* @param rootOfTree 二叉搜索树的根
* @return 双向链表的最小值节点
*/
public BinaryTreeNode convert(BinaryTreeNode rootOfTree){
BinaryTreeNode lastNodeInList = null;
lastNodeInList= ConvertNode(rootOfTree, null);
BinaryTreeNode headOfList = lastNodeInList;
while (headOfList != null && headOfList.left != null)
headOfList = headOfList.left;
return headOfList;
}
/**
* 转换的核心方法,递归地改变链的指向
* @param node 当前树的根节点
* @param lastNodeInList 左链的最小节点
* @return 当前链的最大节点
*/
private BinaryTreeNode ConvertNode(BinaryTreeNode node, BinaryTreeNode lastNodeInList) {
if (node == null)
return null;
if (node.left != null){
lastNodeInList=ConvertNode(node.left,lastNodeInList);
}
node.left = lastNodeInList;
if (lastNodeInList!=null)
lastNodeInList.right = node;
lastNodeInList = node;
if (node.right != null)
lastNodeInList = ConvertNode(node.right,lastNodeInList);
return lastNodeInList;
}
public static void main(String[] args) {
BinaryTreeNode node1 = new BinaryTreeNode(10);
BinaryTreeNode node2 = new BinaryTreeNode(5);
BinaryTreeNode node3 = new BinaryTreeNode(12);
BinaryTreeNode node4 = new BinaryTreeNode(4);
BinaryTreeNode node5 = new BinaryTreeNode(7);
node1.left=node2;
node1.right=node3;
node2.left=node4;
node2.right=node5;
InterQuestions36 test =new InterQuestions36();
BinaryTreeNode list= test.convert(node1);
while (list!=null){
System.out.print(list.nValue +",");
list = list.right;
}
}
/*测试结果
4,5,7,10,12,
*/
算法思路
把二叉搜索树看成3部分,根节点,左子树,右子树。先把左子树完成转换,左子树的最后一个节点是链表的最大值,将其与根节点相连,并把右子树的最小值与根相连。递归进行。