共计 6527 个字符,预计需要花费 17 分钟才能阅读完成。
BAT 作为中国三大最大互联网公司,是每个程序员梦寐以求的工作环境,好比高中生高考的目的是清华北大复旦一样,所以每个年轻程序员都应该尝试进入 BAT 工作。
第一题:搜索二位矩阵
编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:
- 每行的元素从左到右升序排列。
- 每列的元素从上到下升序排列。
示例:
现有矩阵 matrix 如下:
[[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
- 给定 target = 5,返回 true。
- 给定 target = 20,返回 false。
解题思路
二维数组是有规律的:右上角的数字是一列中最小的、一行中最大的,通过这个数字和 target 进行对比,可以将一行或者一列作为候选区域排出,那么 target 可能存在的范围缩小,最终得出结果。
public Boolean searchMatrix(int[][] matrix, int target) {if (matrix.length == 0) {return false;}
for (int i = 0, j = matrix[0].length - 1; i < matrix.length && j >= 0; ) {if (matrix[i][j] > target) {j--;} else if (matrix[i][j] < target) {i++;} else {return true;}
}
return false;
}
第二题:替换空格
请实现一个函数,将一个字符串中的每个空格替换成“%20”。例如,当字符串为 We Are Happy. 则经过替换之后的字符串为 We%20Are%20Happy。
解题思路
- 通过字符串中空格的个数,计算新字符串长度
- 两个指针进行字符串拷贝,当遇到‘’时替换为 %20
public String replaceSpace(StringBuffer str) {char[] chars = str.toString().toCharArray();
StringBuilder res = new StringBuilder();
for (char c : chars) {if (c == '') res.append("%20"); else res.append(c);
}
return res.toString();}
第三题:从头到尾打印链表
输入一个链表,按链表值从尾到头的顺序返回一个 ArrayList。
解题思路
- 栈
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {LinkedList<Integer> stack = new LinkedList<>();
while (listNode != null) {stack.addLast(listNode.val);
listNode = listNode.next;
}
ArrayList<Integer> res = new ArrayList<>();
while (!stack.isEmpty()) {res.add(stack.pollLast());
}
return res;
}
- 递归:当链表过长时,会导致栈溢出
public ArrayList<Integer> printListFromTailToHead(ListNode listNode) {ArrayList<Integer> res = new ArrayList<>();
print(res,listNode);
return res;
}
private void print(ArrayList<Integer> res, ListNode listNode) {if (listNode == null) return;
print(res, listNode.next);
res.add(listNode.val);
}
第四题:重建二叉树
输入某二叉树的前序遍历和中序遍历的结果,请重建出该二叉树。假设输入的前序遍历和中序遍历的结果中都不含重复的数字。例如输入前序遍历序列 {1,2,4,7,3,5,6,8} 和中序遍历序列{4,7,2,1,5,3,8,6},则重建二叉树并返回。
解题思路
- 通过前序遍历找到 root 节点
- 那么在 中序遍历中 root 节点的左侧则是左子树,右侧是右子树
- 依次类推,递归生成节点的左子树和右子树
- 构建过程由下往上
public TreeNode reConstructBinaryTree(int[] pre, int[] in) {Map<Integer, Integer> preIndex = new HashMap<>();
for (int i = 0; i < pre.length; i++) {preIndex.put(pre[i], i);
}
return buildTree(preIndex, in, 0, in.length - 1);
}
private TreeNode buildTree(Map<Integer, Integer> preIndex, int[] in, int start, int end) {if (start == end) {return new TreeNode(in[start]);
}
int indexOfRoot = start;
for (int i = start; i <= end; i++) {if (preIndex.get(in[i]) < preIndex.get(in[indexOfRoot])) {indexOfRoot = i;}
}
TreeNode root = new TreeNode(in[indexOfRoot]);
if (start <= indexOfRoot - 1) root.left = buildTree(preIndex, in, start, indexOfRoot - 1);
if (indexOfRoot + 1 <= end) root.right = buildTree(preIndex, in, indexOfRoot + 1, end);
return root;
}
第五题:用两个栈实现一个队列
用两个栈来实现一个队列,完成队列的 Push 和 Pop 操作。队列中的元素为 int 类型。
解题思路
- 用 stack1 作为 push 队列,将元素 push 到 stack1
- 用 stack2 作为 pop 队列,当 stack2 为空时则将 stack1 的数据 push 到 stack2,否则直接 pop stack2
相当于将两个 stack 拼接:-> stack1 <::> stack2 ->
Stack<Integer> pushStack = new Stack<>();
Stack<Integer> popStack = new Stack<>();
public void push(int node) {pushStack.push(node);
}
public int pop() {if (popStack.isEmpty()) {while (!pushStack.isEmpty()) {popStack.push(pushStack.pop());
}
}
if (popStack.isEmpty()) return -1; else return popStack.pop();}
第六题:旋转数组的最小数字
把一个数组最开始的若干个元素搬到数组的末尾,我们称之为数组的旋转。输入一个非减排序的数组的一个旋转,输出旋转数组的最小元素。例如数组 {3,4,5,1,2} 为{1,2,3,4,5}的一个旋转,该数组的最小值为 1。NOTE:给出的所有元素都大于 0,若数组大小为 0,请返回 0 。
解题思路
- 旋转之后的数组存在两个上升序列,最小元素在两个上升序列的中间
- 用两个指针在两个序列中找到最大和最小的值,这样 end 指向的数则为最小
public int minNumberInRotateArray(int[] array) {if (array.length == 0) {return 0;}
int start = 0, end = array.length - 1;
while (end - start != 1) {int mid = (start + end) / 2;
if (array[mid] >= array[start]) {start = mid;}
if (array[mid] <= array[end]) {end = mid;}
}
return array[end];
}
第七题:斐波纳切数列
大家都知道斐波那契数列,现在要求输入一个整数 n,请你输出斐波那契数列的第 n 项(从 0 开始,第 0 项为 0)。n<=39
解题思路
- 递归计算很慢,是最简单的算法
public int Fibonacci(int n) {if (n == 0) {return 0;}
if (n == 1) {return 1;}
int l = 1, ll = 0;
for (int i = 2; i <= n; i++) {
int t = ll + l;
ll = l;
l = t;
}
return l;
}
第八题:二进制中 1 的个数
输入一个整数,输出该数二进制表示中 1 的个数。其中负数用补码表示。
解题思路
- 负数是补码表示
-
>>>
为无符号右移,>>
为有符号右移,当 n 为负数是会增加多余的 1
public int NumberOf1(int n) {
int mask = 0x01;
int res = 0;
int t = n;
while (t != 0) {if ((t & mask) == 1) {res++;}
t = t >>> 1;
}
return res;
}
第九题:数值的整数次方
给定一个 double 类型的浮点数 base 和 int 类型的整数 exponent。求 base 的 exponent 次方。
解题思路
- 当 n 为偶数时,
$$a^n = a^{n/2} * a^{n/2}$$
- 当 n 为奇数时,
$$a^n = a^{n/2} * a^{n/2} * a$$
- 可以利用类似斐波纳切的方式,利用递归来进行求解
public double Power(double base, int exponent) {if (base == 0) {return 0;}
if (base == 1) {return 1;}
int t_exponent = Math.abs(exponent);
double t = PositivePower(base, t_exponent);
return exponent > 0 ? t : 1 / t;
}
private double PositivePower(double base, int exponent) {if (exponent == 0) {return 1;}
if (exponent == 1) {return base;}
double t = PositivePower(base, exponent >> 1);
t *= t;
if ((exponent & 0x01) == 1) {t *= base;}
return t;
}
第十题:打印最大的 n 位数
输入 n,打印出 1 到最大的 n 位十进制数。比如输入 3,则打印出 1、2、3 直到最大的 3 位数 999。
解题思路
- n 可能很大,导致输出的数字超过 int 或者 long
public void PrintN(int n) {if (n <= 0) {return;}
String res = "0";
while (true) {
Boolean all9 = true;
res = Plus(res, 1);
System.out.println(res);
for (int i = 0; i < res.length(); i++) {if (res.charAt(i) != '9') {
all9 = false;
break;
}
}
if (all9 && res.length() == n) {break;}
}
}
private String Plus(String t, int i) {char[] chars = t.toCharArray();
StringBuilder res = new StringBuilder();
chars[chars.length - 1] += i;
Boolean flag = false;
for (int j = chars.length - 1; j >= 0; j--) {int a = chars[j];
if (flag) {
a++;
flag = false;
}
if (a > '9') {
flag = true;
a = a - '9' + '0' - 1;
}
res.append((char) a);
}
if (flag) {res.append('1');
}
return res.reverse().toString();
}
第十一题:在 O(1)的时间复杂度下删除节点
给定单向链表的头指针以及待删除的指针,定义一个函数在 O(1) 的时间复杂度下删除
解题思路
- 待删除节点非尾节点,将后一个节点的值复制到当前节点,然后删除后一个节点
- 待删除节点为尾节点,从头节点开始,找到待删除节点的前一个节点进行删除
public void O1DeleteNode(ListNode head, ListNode needDelete) {if (needDelete.next != null) {
ListNode next = needDelete.next.next;
needDelete.val = needDelete.next.val;
needDelete.next = next;
} else {
ListNode cursor = head;
while (cursor != null) {if (cursor.next == needDelete) break;
cursor = cursor.next;
}
if (cursor == null) return;
cursor.next = needDelete.next;
}
}
第十二题:调整数组顺序使奇数位于偶数前面
输入一个整数数组,实现一个函数来调整该数组中数字的顺序,使得所有的奇数位于数组的前半部分,所有的偶数位于数组的后半部分,并保证奇数和奇数,偶数和偶数之间的相对位置不变。
解题思路
- 需要保证排序的稳定性
- 采用冒泡算法进行排序
public void reOrderArray(int[] array) {if (array.length <= 1) {return;}
for (int i = array.length - 1; i >= 0; i--) {for (int j = i; j < array.length - 1; j++) {if (array[j] % 2 == 0 && array[j + 1] % 2 == 1) swap(array, j, j + 1);
}
}
}
private void swap(int[] array, int a, int b) {int t = array[a];
array[a] = array[b];
array[b] = t;
}
第十三题:链表中倒数第 k 个结点
输入一个链表,输出该链表中倒数第 k 个结点。
解题思路
- 两个指针,快指针先走 k 步,然后慢指针在向前移动,当快指针遍历结束,慢指针指向倒数第 k 个节点
- 需要考虑倒数 k 个节点不存在的情况
public ListNode FindKthToTail(ListNode head, int k) {if (head == null) {return null;}
ListNode cursor = head;
ListNode cursorK = head;
int i = 0;
while (cursorK != null) {
cursorK = cursorK.next;
if (i >= k) {cursor = cursor.next;}
i++;
}
if (i < k) {return null;}
return cursor;
}
第十五题:反转链表
输入一个链表,反转链表后,输出新链表的表头。
解题思路
- 三个指针
public ListNode ReverseList(ListNode head) {if (head == null || head.next == null) {return head;}
ListNode pre = head, cur = head.next, next;
pre.next = null;
while (cur != null) {
next = cur.next;
cur.next = pre;
pre = cur;
cur = next;
}
return pre;
}
写在最后
面试题还有很多,但是本文章限于篇幅,只分享 15 到一线互联网面试真题;关于本人所设计到的面试题,本人都已经整理成一份完整的 PDF 面试题集合,现免费分享给各位有需要的 Java 工程师朋友,点击下方传送门即可免费领取哟
传送门
- 雄心是成功路上的指南,
- 信心是永不放弃的呼唤,
- 热心是成功者的胸怀,
- 耐心是驱赶困难的利剑,
- 责任心是迈向成功的必然。
- 愿五心伴您度过每一天!
- 衷心祝大家面试一帆风顺,马到成功!