二分查找算法
核心思想
- 在有序数组中通过不断将搜索范围减半来快速找到目标值。
- 时间复杂度:O(log n)。
- 空间复杂度:O(1)(非递归实现)。
代码实现
public int binarySearch(int[] arr, int target) {
int left = 0, right = arr.length - 1;
while (left <= right) {
int mid = left + (right - left) / 2;
if (arr[mid] == target) {
return mid;
} else if (arr[mid] < target) {
left = mid + 1;
} else {
right = mid - 1;
}
}
return -1;
}
面试案例
- 问题:给定一个升序数组
[1, 3, 5, 7, 9]
和目标值 5
,使用二分查找找到目标值的索引。 - 答案:返回索引
2
。
分治算法
核心思想
- 将问题分解为若干个子问题,分别解决后合并结果。
- 经典应用:归并排序、快速排序。
代码实现(归并排序)
public void mergeSort(int[] arr, int left, int right) {
if (left >= right) return;
int mid = left + (right - left) / 2;
mergeSort(arr, left, mid);
mergeSort(arr, mid + 1, right);
merge(arr, left, mid, right);
}
private void merge(int[] arr, int left, int mid, int right) {
int[] temp = new int[right - left + 1];
int i = left, j = mid + 1, k = 0;
while (i <= mid && j <= right) {
if (arr[i] <= arr[j]) {
temp[k++] = arr[i++];
} else {
temp[k++] = arr[j++];
}
}
while (i <= mid) temp[k++] = arr[i++];
while (j <= right) temp[k++] = arr[j++];
System.arraycopy(temp, 0, arr, left, temp.length);
}
面试案例
- 问题:对数组
[38, 27, 43, 3, 9, 82, 10]
进行排序。 - 答案:排序后为
[3, 9, 10, 27, 38, 43, 82]
。
动态规划算法
核心思想
- 将复杂问题分解为子问题,保存子问题的解以避免重复计算。
- 经典应用:背包问题、最长公共子序列、斐波那契数列。
代码实现(斐波那契数列)
public int fibonacci(int n) {
if (n <= 1) return n;
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
面试案例
- 问题:求斐波那契数列第 10 项的值。
- 答案:返回
55
。
KMP算法
核心思想
- 在字符串匹配中利用前缀表(Partial Match Table)避免回溯,提高效率。
- 时间复杂度:O(n + m),其中 n 是主串长度,m 是模式串长度。
代码实现
public int kmpSearch(String text, String pattern) {
int[] lps = computeLPSArray(pattern);
int i = 0, j = 0;
while (i < text.length()) {
if (text.charAt(i) == pattern.charAt(j)) {
i++;
j++;
}
if (j == pattern.length()) {
return i - j;
} else if (i < text.length() && text.charAt(i) != pattern.charAt(j)) {
if (j != 0) {
j = lps[j - 1];
} else {
i++;
}
}
}
return -1;
}
private int[] computeLPSArray(String pattern) {
int[] lps = new int[pattern.length()];
int len = 0, i = 1;
while (i < pattern.length()) {
if (pattern.charAt(i) == pattern.charAt(len)) {
len++;
lps[i] = len;
i++;
} else {
if (len != 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}
return lps;
}
面试案例
- 问题:在字符串
"ABABDABACDABABCABAB"
中查找子串 "ABABCABAB"
的起始位置。 - 答案:返回索引
10
。
贪心算法
核心思想
- 每一步都选择局部最优解,最终希望达到全局最优解。
- 经典应用:活动选择问题、最小生成树(Prim/Kruskal)、霍夫曼编码。
代码实现(活动选择问题)
public int activitySelection(int[] start, int[] end) {
int n = start.length;
int[][] activities = new int[n][2];
for (int i = 0; i < n; i++) {
activities[i][0] = start[i];
activities[i][1] = end[i];
}
Arrays.sort(activities, (a, b) -> a[1] - b[1]);
int count = 1, lastEnd = activities[0][1];
for (int i = 1; i < n; i++) {
if (activities[i][0] >= lastEnd) {
count++;
lastEnd = activities[i][1];
}
}
return count;
}
面试案例
- 问题:给定活动的开始时间和结束时间
[[1, 2], [3, 4], [0, 6], [5, 7]]
,最多可以参加多少个活动? - 答案:返回
3
。
普利姆算法
核心思想
- 构造最小生成树,每次选择当前集合到其他顶点的最小权值边。
- 时间复杂度:O(V^2) 或 O(E log V)(优先队列优化)。
代码实现
public int primMST(int[][] graph) {
int n = graph.length;
boolean[] visited = new boolean[n];
int[] key = new int[n];
Arrays.fill(key, Integer.MAX_VALUE);
key[0] = 0;
int totalWeight = 0;
for (int i = 0; i < n; i++) {
int u = -1;
for (int j = 0; j < n; j++) {
if (!visited[j] && (u == -1 || key[j] < key[u])) {
u = j;
}
}
visited[u] = true;
totalWeight += key[u];
for (int v = 0; v < n; v++) {
if (graph[u][v] != 0 && !visited[v] && graph[u][v] < key[v]) {
key[v] = graph[u][v];
}
}
}
return totalWeight;
}
面试案例
- 问题:给定邻接矩阵
[[0, 2, 0, 6, 0], [2, 0, 3, 8, 5], [0, 3, 0, 0, 7], [6, 8, 0, 0, 9], [0, 5, 7, 9, 0]]
,求最小生成树的总权重。 - 答案:返回
16
。
克鲁斯卡尔算法
核心思想
- 按边权值从小到大排序,依次加入生成树,确保不形成环。
- 时间复杂度:O(E log E)。
代码实现
public int kruskalMST(int[][] edges, int n) {
Arrays.sort(edges, (a, b) -> a[2] - b[2]);
int[] parent = new int[n];
for (int i = 0; i < n; i++) parent[i] = i;
int totalWeight = 0;
for (int[] edge : edges) {
int u = edge[0], v = edge[1], weight = edge[2];
if (find(parent, u) != find(parent, v)) {
union(parent, u, v);
totalWeight += weight;
}
}
return totalWeight;
}
private int find(int[] parent, int x) {
if (parent[x] != x) parent[x] = find(parent, parent[x]);
return parent[x];
}
private void union(int[] parent, int x, int y) {
parent[find(parent, x)] = find(parent, y);
}
面试案例
迪杰斯特拉算法
核心思想
- 单源最短路径算法,适用于非负权图。
- 时间复杂度:O(V^2) 或 O(E log V)(优先队列优化)。
代码实现
public int dijkstra(int[][] graph, int src, int dest) {
int n = graph.length;
int[] dist = new int[n];
boolean[] visited = new boolean[n];
Arrays.fill(dist, Integer.MAX_VALUE);
dist[src] = 0;
for (int i = 0; i < n - 1; i++) {
int u = -1;
for (int j = 0; j < n; j++) {
if (!visited[j] && (u == -1 || dist[j] < dist[u])) {
u = j;
}
}
visited[u] = true;
for (int v = 0; v < n; v++) {
if (graph[u][v] != 0 && !visited[v] && dist[u] + graph[u][v] < dist[v]) {
dist[v] = dist[u] + graph[u][v];
}
}
}
return dist[dest];
}
面试案例
- 问题:给定邻接矩阵
[[0, 10, 0, 0, 0], [10, 0, 5, 0, 0], [0, 5, 0, 20, 1], [0, 0, 20, 0, 2], [0, 0, 1, 2, 0]]
,求从节点 0 到节点 4 的最短路径。 - 答案:返回
16
。
弗洛伊德算法
核心思想
- 多源最短路径算法,适用于任意权图(包括负权图)。
- 时间复杂度:O(V^3)。
代码实现
public void floydWarshall(int[][] graph) {
int n = graph.length;
int[][] dist = new int[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
dist[i][j] = graph[i][j];
}
}
for (int k = 0; k < n; k++) {
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
if (dist[i][k] != Integer.MAX_VALUE && dist[k][j] != Integer.MAX_VALUE &&
dist[i][k] + dist[k][j] < dist[i][j]) {
dist[i][j] = dist[i][k] + dist[k][j];
}
}
}
}
}
面试案例
- 问题:与迪杰斯特拉算法相同的问题。
- 答案:返回
16
。
马踏棋盘算法
核心思想
- 回溯法解决骑士遍历问题,尝试所有可能的移动路径。
- 时间复杂度:O(8(N2))。
代码实现
public boolean knightTour(int N) {
int[][] board = new int[N][N];
int[] dx = {2, 1, -1, -2, -2, -1, 1, 2};
int[] dy = {1, 2, 2, 1, -1, -2, -2, -1};
if (!solveKTUtil(board, 0, 0, 1, dx, dy)) {
System.out.println("Solution does not exist");
return false;
} else {
printSolution(board);
return true;
}
}
private boolean solveKTUtil(int[][] board, int x, int y, int movei, int[] dx, int[] dy) {
if (movei == board.length * board.length) return true;
for (int k = 0; k < 8; k++) {
int next_x = x + dx[k];
int next_y = y + dy[k];
if (isSafe(next_x, next_y, board)) {
board[next_x][next_y] = movei;
if (solveKTUtil(board, next_x, next_y, movei + 1, dx, dy)) return true;
board[next_x][next_y] = -1;
}
}
return false;
}
private boolean isSafe(int x, int y, int[][] board) {
return x >= 0 && x < board.length && y >= 0 && y < board.length && board[x][y] == -1;
}
面试案例
- 问题:在一个 8x8 的棋盘上,从左上角出发,能否完成马踏棋盘?
- 答案:输出解决方案或提示无解。