面试题3:数组中重复的数字。
题目:找出数组中重复的数字。一个长度为n的数组,所有的数字范围在1~n-1内,有些数字重复了,找出任意一个。
- 解决方案1:排序,时间复杂度O(nlogn);
- 解决方案2:利用hash表,需要一个O(n)空间,时间复杂度O(n);
- 解决方案3:比较替换。具体做法:遍历数组,将每个数字放到数值对应的下标位置,若和这个位置的数相同则找到了相同了数字,不相等就交换位置,按相同的逻辑处理交换过来的数字。时间复杂度O(n),不需要另外的空间。
- 解决方案4:不修改数组找出重复的数字,类似二分法。具体做法:取n的中间数值m,若1-m区间的数字个数超过m个就代表这个区间有重复,继续取m的中间值m1重复执行这个逻辑。时间复杂度O(nlogn)。
面试题4:二维数组中的查找。
题目:在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下的递增的顺序排序。输入一个这样的数组和一个整数,判断这个整数是否在二维数组中。
解决方案:直接遍历;还有一种方案,从二维数组的右上角开始遍历,若整数小于这个角的数,只需要在这一行从右向左找是否存在,若大于则跳到下一行去比较。
面试题5:请实现一个函数,把字符串中的每个空格替换成“%20”。
解决方案:遍历一遍字符,计算出空格数量,确定扩容后的字符串长度,从字符串后面开始向前替换填充。
面试题6:从尾到头打印单链表。
解决方案:利用栈,遍历一遍链表依次入栈,再遍历栈。
面试题7:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。
/**
* 面试题7:输入某二叉树的前序遍历和中序遍历的结果,请重建该二叉树。
* 前序遍历:{1, 2, 4, 7, 3, 5, 6, 8}
* 中序遍历:{4, 7, 2, 1, 5, 3, 8, 6}
* Created by Tangwz on 2019/7/17.
*/
public class Seventh {
private static int[] preOrder = {1, 2, 4, 7, 3, 5, 6, 8};
private static int[] inOrder = {4, 7, 2, 1, 5, 3, 8, 6};
public static void main(String[] args) {
if (preOrder == null || inOrder == null || preOrder.length != inOrder.length) {
throw new RuntimeException("please check data.");
}
TreeNode root = createNode(0, preOrder.length - 1, 0, inOrder.length - 1);
System.out.println("验证生成的树");
// 前序遍历
System.out.print("前序遍历:");
preorderTraversal(root);
System.out.println();
// 中序遍历
System.out.print("中序遍历:");
inorderTraversal(root);
}
/**
* @param preStartIndex 前序开始位置
* @param preEndIndex 前序结束位置
* @param inStartIndex 中序开始位置
* @param inEndIndex 中序结束位置
* @return
*/
private static TreeNode createNode(int preStartIndex, int preEndIndex, int inStartIndex, int inEndIndex) {
if (preStartIndex > preEndIndex || inStartIndex > inEndIndex) {
return null;
}
int value = preOrder[preStartIndex];// 前序的第一个值就是分支节点
int mid = -1;//找到在中序遍历中的位置
for (int i = inStartIndex; i <= inEndIndex; i++) {
if (inOrder[i] == value) {
mid = i;
break;
}
}
if (mid == -1) {
throw new RuntimeException("Invalid Input.");
}
TreeNode treeNode = new TreeNode();
treeNode.setValue(value);
int leftLength = mid - inStartIndex;//左子树的长度
if (mid > inStartIndex) {
//构建左子树
treeNode.setLeft(createNode(preStartIndex + 1, preStartIndex + leftLength, inStartIndex, mid - 1));
}
if (mid < inEndIndex) {
//构建右子树
treeNode.setRight(createNode(preStartIndex + leftLength + 1, preEndIndex, mid + 1, inEndIndex));
}
return treeNode;
}
private static void preorderTraversal(TreeNode node) {
if (node != null) {
System.out.print(node.getValue());
preorderTraversal(node.getLeft());
preorderTraversal(node.getRight());
}
}
private static void inorderTraversal(TreeNode root) {
if (root != null) {
inorderTraversal(root.getLeft());
System.out.print(root.getValue());
inorderTraversal(root.getRight());
}
}
}
输出结果:
验证生成的树
前序遍历:12473568
中序遍历:47215386
面试题8:二叉树的下一个节点
题目:给定一棵二叉树和其中一个节点,如何找出中序遍历的下一个节点?树中的节点有指向左右子节点的指针,还有一个指向父节点的指针。
解决方案:考虑两种情况,第一种:此节点有右子树,那么找右子树最左侧那个节点;第二种:无右子树,父节点的右子树是节点自己,继续向上找,直到找到一个它父节点的左子树是它自己的节点,这个节点的父节点就是下一个节点。其他的情况均为找不到下一个节点。
面试题9:用两个栈实现队列
解决方案:stack1用于插入数据,插入元素时依次入stack1;需要删除元素时,判断stack2有无元素,没有将stack1的数据全部出栈压入到stack2中,然后弹出stack2的第一个元素;stack2有元素直接弹出第一个元素。
面试题10:斐波拉契数列
题目:求斐波那契数列的第N项
解决方案:斐波拉契数列
面试题11:旋转数组的最小值
题目:数组{3,4,5,1,2}为{1,2,3,4,5}的一个旋转,最小值为1
解决方案:遍历,时间复杂度为 O(n)。使用二分查找法,旋转之后的数组可以分为两个递增的数组,先确定最小值在哪个递增数组中。比较数组的第一个和最后一个的大小,若小于则第一个就是最小值,若大于等于说明最小值在中间的某个位置,继续。取中间的值,若中间值大于等于第一个值,说明最小值在中间值之后,在第二个数组中去找;若中间值小于等于最后一个值,说明最小值在中间值之前,在第一个数组中去找。第一个值和中间值和最后一个值都相等的情况下,需要遍历所有去查找(例如{1, 1, 1, 0, 1})。
public class RotateArray {
public static void main(String[] args) {
int[] array = {3, 4, 5, 1, 2};
System.out.println(minValueOfRoteArray(array));
int[] array1 = {3, 4, 5, 6, 7, 0, 1, 2, 2, 2, 2, 2, 2};
System.out.println(minValueOfRoteArray(array1));
int[] array2 = {1, 0, 1, 1, 1};
System.out.println(minValueOfRoteArray(array2));
int[] array3 = {1, 1, 1, 0, 1};
System.out.println(minValueOfRoteArray(array3));
int[] array4 = {1, 2, 3, 4, 5};
System.out.println(minValueOfRoteArray(array4));
}
/**
* 二分查找
*/
private static int minValueOfRoteArray(int[] array) {
if (array == null || array.length == 0) {
throw new RuntimeException("Invalid parameters");
}
int midIndex = 0;
// 始终指向第一个有序数组
int firstIndex = 0;
// 始终指向第二个有序数组
int secondIndex = array.length - 1;
// 不成立表示当前数组是或者已旋转为原始有序数组
while (array[firstIndex] >= array[secondIndex]) {
// 最大数与最小数相邻
if (secondIndex - firstIndex == 1) {
midIndex = secondIndex;
break;
}
midIndex = (firstIndex + secondIndex) / 2;
// 特殊情况前中后三个指针所对应的值全部一样,切换为顺序查找 {10111} 和 {11101} 无法判断
if (array[firstIndex] == array[midIndex] && array[midIndex] == array[secondIndex]) {
return searchMidValue(array, firstIndex, secondIndex);
}
if (array[firstIndex] <= array[midIndex]) {
firstIndex = midIndex;
} else if (array[secondIndex] >= array[midIndex]) {
secondIndex = midIndex;
}
}
return array[midIndex];
}
private static int searchMidValue(int[] array, int firstIndex, int secondIndex) {
int value = array[firstIndex];
for (int i = firstIndex + 1; i <= secondIndex; i++) {
if (value > array[i]) {
return array[i];
}
}
return value;
}
}
面试题12:矩阵中的路径
题目:请设计一个函数,用来判断在一个矩阵中是否存在一条包含某字符串中所有字符的路径。可以从矩阵任意一格开始,能上下左右移动,但是不能重入。
public class HasPath {
private static ThreadLocal<Integer> pathLength = new ThreadLocal<>();
public static void main(String[] args) {
int[][] matrix = {
{'a', 'b', 'd', 't'},
{'c', 'f', 'c', 's'},
{'j', 'd', 'e', 'h'}
};
String[] strings = {"abdt", "", "bfce", "bfcea"};
for (String str : strings) {
System.out.println(hasPath(matrix, matrix.length, matrix[0].length, str.toCharArray()));
}
}
private static boolean hasPath(int[][] matrix, int rows, int cols, char[] chars) {
boolean[][] hasVisited = new boolean[rows][cols];
pathLength.set(0);
for (int row = 0; row < rows; row++) {
for (int col = 0; col < cols; col++) {
if (hasPathCore(matrix, rows, cols, row, col, chars, hasVisited)) {
return true;
}
}
}
return false;
}
private static boolean hasPathCore(int[][] matrix, int rows, int cols, int row, int col, char[] chars, boolean[][] hasVisited) {
Integer length = pathLength.get();
if (length >= chars.length) {
return true;
}
boolean hasPath = false;
if (row >= 0 && row < rows && col >= 0 && col < cols
&& !hasVisited[row][col]
&& matrix[row][col] == chars[length]) {
pathLength.set(pathLength.get() + 1);
hasVisited[row][col] = true;
hasPath = hasPathCore(matrix, rows, cols, row, col - 1, chars, hasVisited)
|| hasPathCore(matrix, rows, cols, row, col + 1, chars, hasVisited)
|| hasPathCore(matrix, rows, cols, row - 1, col, chars, hasVisited)
|| hasPathCore(matrix, rows, cols, row + 1, col, chars, hasVisited);
if (!hasPath) {
pathLength.set(pathLength.get() - 1);
hasVisited[row][col] = false;
}
}
return hasPath;
}
}
面试题13:减绳子
题目:给你一根长度为n的绳子,请把绳子减成m段(n,m都为整数),求这m段长度乘积的最大值。
解决方案1:循环求解。
解决方案2:动态规划,求一个问题的最优解;整体问题的最优解依赖于小问题的最优解;小问题之间还有相互重叠的子问题。
private static int maxProductAfterCutting(int length) {
if (length < 2) {
return 0;
}
if (length == 2) {
return 1;
}
if (length == 3) {
return 2;
}
int[] products = new int[length + 1];
products[0] = 0;
products[1] = 1;
products[2] = 2;
products[3] = 3;//长度为3,不减就是最大值
int max;
for (int i = 4; i <= length; i++) {
max = 0;
for (int j = 1; j <= i / 2; j++) {
int product = products[j] * products[i - j];
if (max < product) {
max = product;
}
}
products[i] = max;
}
return products[length];
}
解决方案3:贪婪算法。挡剩余绳子长度大于等于5时尽可能的将绳子多减出长度为3的段,当剩余绳子长度为4的时候不能减3需要减成两个2。
private static int greedyMethod(int length) {
if (length < 2) {
return 0;
}
if (length == 2) {
return 1;
}
if (length == 3) {
return 2;
}
// 尽可能多剪出3或者2,当剩余4时不用减3
int numOf3 = length / 3;
if (length - 3 * numOf3 == 1) {
numOf3 -= 1;
}
int numOf2 = (length - numOf3 * 3) / 2;
return (int) (Math.pow(3, numOf3) * Math.pow(2, numOf2));
}
面试题15:二进制中1的个数
public void fifteenth() {
/**
* 二进制中1的个数
*/
for (int i = -3; i < 5; i++) {
System.out.println(getNumberOfOne1(i));
System.out.println(getNumberOfOne2(i));
System.out.println(getNumberOfOne3(i));
System.out.println();
}
}
private int getNumberOfOne1(int n) {
int count = 0;
while (n != 0) {
if ((n & 1) > 0) {
count++;
}
n >>>= 1;
}
return count;
}
private int getNumberOfOne2(int n) {
int count = 0;
int flag = 1;
while (flag != 0) {
if ((n & flag) != 0) {
count++;
}
flag <<= 1;
}
return count;
}
private int getNumberOfOne3(int n) {
int count = 0;
while (n != 0) {
count++;
n = n & (n - 1);
}
return count;
}
面试题16:数值的整数次方
题目:实现函数 double power(double base, int exponent),求 base 的 exponent 次方。不考虑大数问题。
public void sixteenth() {
System.out.println(doublePower(1.2D, 2));
System.out.println(doublePower(1.2D, 1));
System.out.println(doublePower(1.2D, 0));
System.out.println(doublePower(2D, -2));
System.out.println(doublePower(2D, 3));
System.out.println(doublePower(2D, 6));
System.out.println(doublePower(2D, 10));
}
private double doublePower(double base, int exponent) {
if (base == 0D) {
return 0D;
}
if (exponent == 0) {
return 1D;
}
int absExponent = exponent;
if (exponent < 0) {
absExponent = -exponent;
}
double result = exclusivePowerResultWithUnsigned(base, absExponent);
double result1 = advancedPowerResultWithUnsignedWithRecursive(base, absExponent);
double result2 = advancedPowerResultWithUnsignedWithoutRecursive(base, absExponent);
if (exponent < 0) {
result2 = 1 / result;
}
return result2;
}
private double exclusivePowerResultWithUnsigned(double base, int absExponent) {
double result = base;
for (int i = 1; i < absExponent; i++) {
result = result * base;
}
return result;
}
private double advancedPowerResultWithUnsignedWithRecursive(double base, int absExponent) {
if (absExponent == 1) {
return base;
}
double result = advancedPowerResultWithUnsignedWithRecursive(base, absExponent >> 1);
result = result * result;
if ((absExponent & 1) == 1) {
result = result * base;
}
return result;
}
private double advancedPowerResultWithUnsignedWithoutRecursive(double base, int absExponent) {
double result = 1D;
double temp = base;
while (absExponent > 0) {
if ((absExponent & 1) == 1) {
result = result * temp;
}
absExponent >>= 1;
if (absExponent > 0) {
temp = temp * temp;
}
}
return result;
}
面试题17:打印从1到最大的n位数
题目:输入数字n,按顺序打印从1到最大n位的十进制数。输入3,打印1、2、3、… 、100、101、… 、999。
解决方案1:使用字符串模拟数字相加
public void seventeenth1(){
Integer number = Integer.valueOf("3");
byte[] bytes = new byte[number];
while(incrementOne(bytes)){
printBytes(bytes);
System.out.println();
}
}
private static boolean incrementOne(byte[] bytes) {
int length = bytes.length;
//从低位开始增加1,若进位发生在下标为0的位置就返回false
for (int i = length - 1; i >= 0; i--) {
byte value = bytes[i];
if (value == 9) {
if (i == 0) {
return false;
}
bytes[i] = 0;
} else {
bytes[i] = (byte) (value + 1);
return true;
}
}
return false;
}
解决方案2:可以看做是求n的数的全排列
public void seventeenth2(){
Integer number = Integer.valueOf("3");
byte[] bytes = new byte[number];
for (byte i = 0; i < 10; i++) {
bytes[0] = i;
printRecursive(bytes, number, 0);
}
}
private static void printRecursive(byte[] bytes, Integer length, int index) {
if (length == index + 1) {
printBytes(bytes);
System.out.println();
return;
}
for (byte i = 0; i < 10; i++) {
bytes[index + 1] = i;
printRecursive(bytes, length, index + 1);
}
}
private static void printBytes(byte[] bytes) {
boolean isBegin0 = true;
for (byte aByte : bytes) {
if (isBegin0 && aByte != 0) {
isBegin0 = false;
}
if (!isBegin0) {
System.out.print(aByte);
}
}
}
面试题18:删除链表的节点
题目:在O(1)时间内删除链表节点。给定一个单链表的头指针和某个节点的指针。
解决方案:直接删除无法做到,模拟删除,将下一个节点的内容移动到此节点。删非尾节点为O(1),删只有一个节点的链表也为O(1),删尾节点为O(n)需要找到前驱节点。
面试题21:调整数组顺序使奇数位于偶数前面
解决方案1:常规遍历,遇到一个偶数就取出,把后面的所有节点向前移动一格,在后面放入取出的偶数,时间复杂度O(n2)。
解决方案2:常规遍历,遇到一个偶数,和最后面的奇数节点进行交换,再移动指针,时间复杂度O(n)。
public void twentyFirst1() {
Integer[] arrays = {1, 3, 5, 6, 8, 6, 4, 2};
int firstIndex = 0;
int lastIndex = arrays.length - 1;
while (firstIndex < lastIndex) {
//从开头找到第一个偶数
while (firstIndex < lastIndex && arrays[firstIndex] % 2 != 0) {
firstIndex++;
}
//从末尾找到第一个奇数
while (firstIndex < lastIndex && arrays[lastIndex] % 2 == 0) {
lastIndex--;
}
if (firstIndex < lastIndex) {
int temp = arrays[firstIndex];
arrays[firstIndex] = arrays[lastIndex];
arrays[lastIndex] = temp;
}
}
System.out.println(Arrays.deepToString(arrays));
}
解决方案3:考虑可扩展,让划分的条件可以动态变换,例如负数在整数前面、能被3整除的在前面。
public void twentyFirst2() {
Integer[] arrays = {1, 3, 5, -6, -8, 6, 4, 2};
int firstIndex = 0;
int lastIndex = arrays.length - 1;
//修改条件,只需要改变这里的函数表达式,正数在前
Predicate<Integer> predicate = (Integer i) -> i < 0;
while (firstIndex < lastIndex) {
while (firstIndex < lastIndex && !predicate.test(arrays[firstIndex])) {
firstIndex++;
}
while (firstIndex < lastIndex && predicate.test(arrays[lastIndex])) {
lastIndex--;
}
if (firstIndex < lastIndex) {
int temp = arrays[firstIndex];
arrays[firstIndex] = arrays[lastIndex];
arrays[lastIndex] = temp;
}
}
System.out.println(Arrays.deepToString(arrays));
}
面试题22:链表中倒数第K个节点
题目:单链表,倒数第一个节点为尾节点。
解决方案1:常规解法,遍历一遍链表确定长度 length,再从头开始遍历长度(length-K);
解决方案2:如何只遍历一遍,使用两个指针,一个快一个慢,快的先遍历k个长度,然后慢的再和快的一起遍历,直到尾节点,慢的那个就代表第K个节点。
面试题23:链表中环的入口节点
解决方案:先确定是否有环,再确定环的长度 length,再找到倒数第 length 个节点。
public void twentyThird() {
LinkedNode<Integer> headLoopNode = createHeadLoopNode();
LinkedNode<Integer> meetingNode = MeetingNode(headLoopNode);
if (meetingNode != null) {
int loopLength = 1;
LinkedNode<Integer> meetingNodeNext = meetingNode.getNext();
while (meetingNode != meetingNodeNext) {
meetingNodeNext = meetingNodeNext.getNext();
loopLength++;
}
LinkedNode<Integer> node1 = headLoopNode;
LinkedNode<Integer> node2 = headLoopNode;
while (loopLength > 0) {
loopLength--;
node1 = node1.getNext();
}
while (node1 != node2) {
node1 = node1.getNext();
node2 = node2.getNext();
}
System.out.println(node1.getValue());
}
}
public LinkedNode<Integer> createHeadLoopNode() {
LinkedNode<Integer> node5 = new LinkedNode<>();
node5.setValue(5);
LinkedNode<Integer> node4 = new LinkedNode<>();
node4.setValue(4);
node4.setNext(node5);
LinkedNode<Integer> node3 = new LinkedNode<>();
node3.setValue(3);
node3.setNext(node4);
LinkedNode<Integer> node2 = new LinkedNode<>();
node2.setValue(2);
node2.setNext(node3);
LinkedNode<Integer> node1 = new LinkedNode<>();
node1.setValue(1);
node1.setNext(node2);
LinkedNode<Integer> rootNode = new LinkedNode<>();
rootNode.setValue(0);
rootNode.setNext(node1);
// 设置一个环
node5.setNext(node1);
return rootNode;
}
private LinkedNode<Integer> MeetingNode(LinkedNode<Integer> headLoopNode) {
if (headLoopNode == null) {
return null;
}
LinkedNode<Integer> slowNode = headLoopNode.getNext();
if (slowNode == null) {
return null;
}
LinkedNode<Integer> fastNode = slowNode.getNext();
while (slowNode != null && fastNode != null) {
if (fastNode == slowNode) {
return slowNode;
}
slowNode = slowNode.getNext();
fastNode = fastNode.getNext();
if (fastNode != null) {
fastNode = fastNode.getNext();
}
}
return null;
}
面试题24:反转链表
public void twentyFourth() {
LinkedNode<Integer> headNode = LinkedNode.createHeadNode();
System.out.println(headNode);
LinkedNode<Integer> reverseHeadNode = null;
LinkedNode<Integer> node = headNode;
LinkedNode<Integer> preNode = null;
while (node != null) {
reverseHeadNode = node;
node = node.getNext();
reverseHeadNode.setNext(preNode);
preNode = reverseHeadNode;
}
System.out.println(reverseHeadNode);
}
输出结果:
LinkedNode(value=0, next=LinkedNode(value=1, next=LinkedNode(value=2, next=LinkedNode(value=3, next=LinkedNode(value=4, next=LinkedNode(value=5, next=null))))))
LinkedNode(value=5, next=LinkedNode(value=4, next=LinkedNode(value=3, next=LinkedNode(value=2, next=LinkedNode(value=1, next=LinkedNode(value=0, next=null))))))
面试题25:合并两个排序的链表
public void twentyFifth() {
LinkedNode<Integer> headNode1 = LinkedNode.createHeadNode();
LinkedNode<Integer> headNode2 = LinkedNode.createHeadNode();
LinkedNode<Integer> mergeNode = mergeNode(headNode1, headNode2);
System.out.println(mergeNode);
}
private LinkedNode<Integer> mergeNode(LinkedNode<Integer> headNode1, LinkedNode<Integer> headNode2) {
if (headNode1 == null) {
return headNode2;
} else if (headNode2 == null) {
return headNode1;
} else {
LinkedNode<Integer> tempNode = null;
if (headNode1.getValue() <= headNode2.getValue()) {
tempNode = headNode1;
tempNode.setNext(mergeNode(headNode1.getNext(), headNode2));
} else {
tempNode = headNode2;
tempNode.setNext(mergeNode(headNode1, headNode2.getNext()));
}
return tempNode;
}
}
面试题26:树的子结构
题目:输入两棵二叉树A和B,判断B是不是A的子结构。
解决方案:先比较头节点是否一致,不一致遍历A找到和B头节点一致的子节点C。接着比较C节点的树是否包含B树,递归比较左右子节点。
public void twentySixth() {
TreeNode root1 = createNode(0, 7, 0, 7);
TreeNode root2 = createNode(0, 7, 0, 7);
boolean flag = hasSubTree(root1, root2);
System.out.println(flag);
}
private boolean hasSubTree(TreeNode root1, TreeNode root2) {
boolean result = false;
if (root1 != null && root2 != null) {
if (root1.getValue().equals(root2.getValue())) {
result = doseTree1HaveTree2(root1, root2);
}
if (!result) {
result = hasSubTree(root1.getLeft(), root2);
}
if (!result) {
result = hasSubTree(root1.getRight(), root2);
}
}
return result;
}
private boolean doseTree1HaveTree2(TreeNode root1, TreeNode root2) {
if (root1 == null && root2 != null) {
return false;
}
if (root2 == null) {
return true;
}
if (!root1.getValue().equals(root2.getValue())) {
return false;
}
return doseTree1HaveTree2(root1.getLeft(), root2.getLeft())
&& doseTree1HaveTree2(root1.getRight(), root2.getRight());
}