代码面试最常用的10大算法
摘要
面试也是一门学问,在面试之前做好充分的准备是成功的必要条件。程序员在代码面试时,常会遇到编写算法的相关问题,比如排序、二叉树遍历等等。
在程序员的职业生涯中,算法亦算是一门基础课程。尤其是在面试环节,很多公司都会要求程序员编写一些算法实例,例如快速排序、二叉树查找等。
这篇文章总结了编码面试中的常见主题,包括:1) 字符串/数组/矩阵,2) 链表,3) 树,4) 堆,5) 图,6) 排序,7) 动态规划,8) 位操作,9) 组合和排列,以及 10) 数学。将问题归为一类总是不容易的,因为很多问题可能属于多个类别。
1. 字符串/数组
算法问题的输入通常是字符串或数组。在没有 IDE 自动完成的情况下,应记住以下常用方法:
toCharArray() // 获取 String 的 char 数组
charAt(int x) // 获取特定索引处的 char
length() // 字符串长度
length // 数组大小
substring(int beginIndex)
substring(int beginIndex, int endIndex)
Integer.valueOf() // 字符串转整数
String.valueOf() // 整数转字符串
Arrays.sort() // 数组排序
Arrays.toString(char[] a) // 转换为字符串
Arrays.copyOf(T[] original, int newLength)
System.arraycopy(Object src, int srcPos, Object dest, int destPos, int length)经典问题:
- 旋转数组,字符串中的反向单词
- 评估反向波兰符号(堆栈)
- 同构字符串
- 字梯(BFS),字梯 II(BFS)
- 两个排序数组的中位数
- 数组中第 K 个最大的元素
- 通配符匹配,正则表达式匹配
- 合并间隔,插入间隔
- 两数之和,两数之和 II,两数之和 III,3Sum,4Sum
- 3Sum 最接近
- 字符串转整数 (atoi)
- 合并排序的数组
- 有效括号
- 最长有效括号
- 实现 strStr()
- 最小大小子数组总和
- 搜索插入位置
- 最长连续序列
- 有效回文
- 之字形转换
- 添加二进制数
- 最后一个字的长度
- 三角形
- 包含重复项:I,II,III
- 从已排序数组中删除重复项:I,II,Remove Element,移动零
- 最长的子字符串而不重复字符
- 包含 2 个唯一字符的最长的子字符串 [Google]
- 具有所有单词串联的子串
- 最小窗口子串
- 在旋转排序数组中找到最小值:I,II
- 在旋转数组中搜索:I,II
- 最小堆栈
- 多数元素:I,II
- 公牛和母牛
- 直方图中最大的矩形
- 最长的公共前缀 [Google]
- 最大的数字
- 简化路径
- 比较版本号
- 加油站
- 帕斯卡三角形:I,II
- 装满水的容器
- 糖果 [Google]
- 捕获雨水
- 数数并说
- 搜索范围
- 基本计算器,基本计算器 II
- 组字母图
- 最短回文
- 矩形区域
- 摘要范围
- 增加三重子序列
- 使用目标数字列表和算术运算
- 字符串的反元音
- 翻转游戏,翻转游戏 II
- 缺少数字,找到重复的数字,第一个缺失正数
- 有效字谜,组移位的字符串
- 前 K 个常见元素
- 查找峰值元素
- 字模式,字型 II
- H-Index,H-Index II
- 回文对
- 一个编辑距离
- 扰乱字符串
- 第一个不良版本
- 整数以英语单词
- 文本对齐方式
- 删除无效的括号
- 两个数组的交集,两个数组的交集 II
- 滑动窗口最大值,数据流的移动平均值
- 猜测数更高或更低
2. 矩阵
解决矩阵相关问题的常用方法包括 DFS、BFS、动态规划等。
经典问题:
- 设置矩阵零点
- 螺旋矩阵
- 螺旋矩阵 II
- 搜索 2D 矩阵
- 搜索 2D 矩阵 II
- 旋转图像 [Palantir]
- 有效数独
- 最小路径总和(DP) [Google]
- 唯一路径(DP) [Google]
- 唯一路径 II(DP)
- 岛屿数量(DFS / BFS),岛屿数量 II(不交集),无向图中连接的组件数
- 周围区域(BFS)
- 最大矩形
- 最大正方形
- 单词搜索(DFS)
- 单词搜索 II
- 范围总和查询 2D – 不可变
- 矩阵中最长的增加路径(DFS)
- 与所有建筑物的最短距离
- 生命游戏
- 油漆房,油漆房 II
- 数独解算器(DFS)
- 墙和门(DFS / BFS)
- 井字游戏
- 最佳集合点
3. 链表
在 Java 中,链表的实现非常简单。每个节点都有一个值和到下一个节点的链接。
class Node {
int val;
Node next;
Node(int x) {
val = x;
next = null;
}
}链表的两个流行应用是堆栈和队列。
Stack
class Stack{
Node top;
public Node peek(){
if(top != null){
return top;
}
return null;
}
public Node pop(){
if(top == null){
return null;
} else {
Node temp = new Node(top.val);
top = top.next;
return temp;
}
}
public void push(Node n){
if(n != null){
n.next = top;
top = n;
}
}
}Queue
class Queue{
Node first, last;
public void enqueue(Node n){
if(first == null){
first = n;
last = first;
} else {
last.next = n;
last = n;
}
}
public Node dequeue(){
if(first == null){
return null;
} else {
Node temp = new Node(first.val);
first = first.next;
return temp;
}
}
}Java 标准库包含一个名为 Stack 的类。Java SDK 中的另一个类是 LinkedList,它可用作队列(add() 和 remove())。(LinkedList 实现 Queue 接口。)如果在面试过程中需要堆栈或队列来解决问题,则可以使用它们。
经典问题:
- 使用数组实现堆栈
- 添加两个数字
- 重新排序列表
- 链接列表循环
- 使用随机指针复制列表
- 合并两个排序的列表
- 奇偶链接列表
- 从排序中删除重复项列表:I,II
- 分区列表
- LRU 缓存
- 两个链接列表的交集
- 删除链接列表元素
- 成对交换节点
- 反向链接列表,反向链接列表 II,打印链接列表以相反的顺序
- 从列表末尾删除第 N 个节点(快慢指针)
- 使用队列实现堆栈
- 使用堆栈实现队列
- 回文链接列表
- 使用数组实现队列
- 链接列表中的删除节点
- k 组中的反向节点
4. 树、堆与字典树
树通常是指二叉树。每个节点都包含一个左节点和一个右节点,如下所示:
class TreeNode{
int value;
TreeNode left;
TreeNode right;
}以下是一些与树相关的概念:
- 二叉搜索树 (Binary Search Tree):对于所有节点,左子节点 <= 当前节点 <= 右子节点。
- 平衡与不平衡:在平衡树中,每个节点的左右子树的深度相差 1 或更小。
- 完整的二叉树:除叶子外的每个节点都有两个孩子。
- 完美二叉树:完整的二叉树,其中所有叶子处于相同深度或相同级别,并且每个父级都有两个子级。
- 完全二叉树:一个二叉树,其中每个级别(除了最后一个级别)都已完全填充,并且所有节点都尽可能地靠左。
堆) 是满足堆属性的基于树的专用数据结构。其操作的时间复杂度很重要(例如,find-min,delete-min,insert 等)。在 Java 中,必须了解 PriorityQueue。
4.1 树
- 二叉树的遍历:前序,中序,后序,层序,层序 II,垂直顺序
- 反转二叉树
- 在 BST 中第 K 个最小元素
- 二叉树最长连续序列
- 确认二叉搜索树
- 平铺二叉树到链表
- 路径总和:I(DFS 或 BFS),II(DFS)
- 构造二叉树:从有序和后序遍历,从前序和有序遍历
- 将排序的数组转换为二叉搜索树 [Google]
- 将排序后的列表转换为二叉搜索树 [Google]
- 二叉树的最小深度
- 二叉树的最大路径总和
- 平衡二叉树
- 对称树
- 二叉搜索树迭代器
- 二叉树右侧视图
- 二叉搜索树的最低共同祖先
- 二叉树的最低公共祖先
- 验证二叉树的前序序列化
- 在每个节点中填充下一个右指针
- 在每个节点 II 中填充下一个右指针
- 唯一的二叉搜索树:I(DP),II(DFS)
- 根到叶的总数(DFS)
- 计数完整树节点
- 最近的二叉搜索树值
- 二叉树路径
- 二叉树的最大深度
- 恢复二叉搜索树
- 相同树
- 对二叉树进行序列化和反序列化
- 在 BST 中顺序排序
- 查找二叉树的叶子
- 最大的 BST 子树
4.2 堆
4.3 字典树 (Trie)
4.4 段树
5. 图
与图相关的问题主要集中在深度优先搜索 (DFS) 和广度优先搜索 (BFS)。深度优先搜索非常简单,您可以从根节点开始遍历所有邻居。
以下是图形和广度优先搜索的简单实现。关键是使用队列存储节点。

1) 定义 GraphNode
class GraphNode{
int val;
GraphNode next;
GraphNode[] neighbors;
boolean visited;
GraphNode(int x) {
val = x;
}
GraphNode(int x, GraphNode[] n){
val = x;
neighbors = n;
}
public String toString(){
return "value: "+ this.val;
}
}2) 定义 Queue
class Queue{
GraphNode first, last;
public void enqueue(GraphNode n){
if(first == null){
first = n;
last = first;
} else {
last.next = n;
last = n;
}
}
public GraphNode dequeue(){
if(first == null){
return null;
} else {
GraphNode temp = new GraphNode(first.val, first.neighbors);
first = first.next;
return temp;
}
}
}3) 广度优先搜索使用队列
public class GraphTest {
public static void main(String[] args) {
GraphNode n1 = new GraphNode(1);
GraphNode n2 = new GraphNode(2);
GraphNode n3 = new GraphNode(3);
GraphNode n4 = new GraphNode(4);
GraphNode n5 = new GraphNode(5);
n1.neighbors = new GraphNode[]{n2,n3,n5};
n2.neighbors = new GraphNode[]{n1,n4};
n3.neighbors = new GraphNode[]{n1,n4,n5};
n4.neighbors = new GraphNode[]{n2,n3,n5};
n5.neighbors = new GraphNode[]{n1,n3,n4};
breathFirstSearch(n1, 5);
}
public static void breathFirstSearch(GraphNode root, int x){
if(root.val == x)
System.out.println("find in root");
Queue queue = new Queue();
root.visited = true;
queue.enqueue(root);
while(queue.first != null){
GraphNode c = (GraphNode) queue.dequeue();
for(GraphNode n: c.neighbors){
if(!n.visited){
System.out.print(n + " ");
n.visited = true;
if(n.val == x)
System.out.println("Find "+n);
queue.enqueue(n);
}
}
}
}
}Output:
value: 2 value: 3 value: 5 Find value: 5
value: 4经典问题:
6. 排序
不同排序算法的时间复杂度。您可以转到 Wiki 来查看它们的基本概念。
| Algorithm | Average Time | Worst Time | Space |
|---|---|---|---|
| Bubble sort | n^2 | n^2 | 1 |
| Selection sort | n^2 | n^2 | 1 |
| Insertion sort | n^2 | n^2 | 1 |
| Quick sort | n log(n) | n^2 | log(n) |
| Merge sort | n log(n) | n log(n) | depends |
注:BinSort、Radix Sort 和 CountSort 与其他假设使用不同的假设集,因此它们不是“常规”排序方法。(感谢 Fidel 指出这一点)
这是一些实现/演示,此外,您可能想了解 Java 开发人员在实践中的排序方式。
7. 动态规划
动态规划是一种用于解决具有以下属性的问题的技术:
- 使用较小实例的解决方案来解决实例。
- 对于较小实例的解决方案可能需要多次。
- 较小实例的解决方案存储在表中,因此每个较小实例仅解决一次。
- 额外的空间用于节省时间。
攀登台阶的问题完全适合这 4 个属性。因此,可以通过使用动态规划来解决。
public static int[] A = new int[100];
public static int f3(int n) {
if (n <= 2)
A[n] = n;
if(A[n] > 0)
return A[n];
else
A[n] = f3(n-1) + f3(n-2); // 存储结果,因此只计算一次!
return A[n];
}经典问题:
- 编辑距离
- 不同的子序列总数
- 最长的回文子串
- 断字:I,II
- 最大子数组:I,最大乘积子数组
- 回文分区:I,II
- 强盗 (House Robber):I [Google],II,III
- 跳跃游戏:I,II
- 最佳买卖股票:I,II,III,IV
- 地下城游戏
- 最小路径总和
- 唯一路径
- 解码方式
- 最长的公共子序列
- 最长的公共子串
- 最长的增长子序列
- 硬币找零
- 完美正方形
8. 位操作
位运算符:
| OR (\ | ) | AND (&) | XOR (^) | Left Shift (<<) | Right Shift (>>) | Not (~) |
|---|---|---|---|---|---|---|
| 1\ | 0=1 | 1&0=0 | 1^0=1 | 0010<<2=1000 | 1100>>2=0011 | ~1=0 |
得到第 i 个给定数字 n。(我从 0 开始计数,从右开始)
public static boolean getBit(int num, int i){
int result = num & (1<<i);
if(result == 0){
return false;
} else {
return true;
}
}例如,获取数字 10 的第二位。
i=1, n=10
1<<1= 10
1010 & 10 = 10
10 is not 0, so return true;经典问题:
9. 组合和排列
组合和排列之间的区别在于顺序是否重要。
范例 1:
给定 5 个数字 -1、2、3、4 和 5,打印出 5 个数字的不同顺序。4 不能是第三个,3 和 5 不能相邻。有多少种不同的组合?
范例 2:
给定 5 个 banaba,4 个梨和 3 个苹果,假设一种水果是相同的,那么有多少种不同的组合?
类问题:
10. 数学
解决数学问题通常需要我们从观察中找到规律性或重复模式。如果您没有任何想法,请首先列出一小部分数字的结果。
- 反转整数
- 回文编号
- Pow(x, n):Pow(x, n),2 的幂,3 的幂,4 的幂
- 子集
- 子集 II
- 分数循环小数 [Google]
- Excel 工作表列号
- Excel 工作表列标题
- 阶乘尾随零
- 快乐数
- 计数素数
- 加一
- 除以两个整数
- 乘法字符串
- 一行上的最大点
- 数组乘积(自我除外)
- 整数中断
- 添加数字
- 丑数:I,II,超级丑数,求和最小的 K 对
11. HashMap
附加问题:
其他资源
相关文章
说明: 本文引用的部分题目链接及内容源自 2012-2016 年期间的整理,部分 LeetCode 题目编号或名称可能随平台更新有所变化,但核心算法思想与分类依然具有参考价值。
版权声明:本文为原创文章,版权归 戴老师的博客 所有,转载请联系博主获得授权。
本文地址:https://1diff.fun/archives/dai-ma-mian-shi-zui-chang-yong-de-10-da-suan-fa.html
如果对本文有什么问题或疑问都可以在评论区留言,我看到后会尽量解答。