返回
驾驭算法题的通用武器:实用思想剖析
见解分享
2023-11-15 09:30:24
刷题第三弹:算法通用思想
这篇文章是关于如何刷题的第三篇文章,或许也是最终章了。这一章,我们将探讨一些几乎所有算法题都能用到的超实用思想。
我们先来看一下前两篇文章的地址:
如果你还没有看过前两篇文章,我建议你先去看看。
今天,我将兑现当初吹下的牛皮。话不多说,直接上干货。如果你觉得这篇文章有用,请三连支持我,让我能够坚持下去,给大家带来更多的干货。
动态规划
动态规划是一种自顶向下的算法,它将问题分解成子问题,并存储子问题的解,以避免重复计算。动态规划特别适合解决具有重叠子问题和最优子结构性质的问题。
-
步骤:
- 将问题分解成子问题。
- 确定子问题的重叠性。
- 使用存储或备忘录记录子问题的解。
- 自底向上地解决子问题。
-
示例: 斐波那契数列
public int fib(int 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];
}
贪心
贪心算法是一种自底向上的算法,它在每一步都做出局部最优选择,期望最后得到全局最优解。贪心算法不保证总是能得到全局最优解,但对于某些问题,它可以提供一个近似最优解。
-
步骤:
- 定义局部最优准则。
- 在每一步中,选择满足局部最优准则的选项。
- 重复步骤 2,直到问题解决。
-
示例:活动选择问题
public List<Activity> selectActivities(List<Activity> activities) {
Collections.sort(activities, (a, b) -> a.endTime - b.endTime);
List<Activity> selectedActivities = new ArrayList<>();
selectedActivities.add(activities.get(0));
int lastActivityEndTime = activities.get(0).endTime;
for (int i = 1; i < activities.size(); i++) {
Activity activity = activities.get(i);
if (activity.startTime >= lastActivityEndTime) {
selectedActivities.add(activity);
lastActivityEndTime = activity.endTime;
}
}
return selectedActivities;
}
回溯
回溯算法是一种深度优先搜索算法,它通过尝试所有可能的解,并回溯到上一步尝试其他解,来解决问题。回溯算法特别适合解决组合优化问题。
-
步骤:
- 定义搜索空间。
- 逐层搜索搜索空间,在每个节点处做出选择。
- 如果选择导致无效解,则回溯到上一步并尝试其他选择。
- 找到满足约束条件的解。
-
示例:八皇后问题
public boolean solveNQueens(int n) {
char[][] board = new char[n][n];
for (int i = 0; i < n; i++) {
for (int j = 0; j < n; j++) {
board[i][j] = '.';
}
}
return solveNQueens(board, 0);
}
private boolean solveNQueens(char[][] board, int row) {
if (row == board.length) {
return true;
}
for (int col = 0; col < board.length; col++) {
if (isSafe(board, row, col)) {
board[row][col] = 'Q';
if (solveNQueens(board, row + 1)) {
return true;
}
board[row][col] = '.';
}
}
return false;
}
private boolean isSafe(char[][] board, int row, int col) {
for (int i = 0; i < row; i++) {
if (board[i][col] == 'Q') {
return false;
}
}
for (int i = row, j = col; i >= 0 && j >= 0; i--, j--) {
if (board[i][j] == 'Q') {
return false;
}
}
for (int i = row, j = col; i >= 0 && j < board.length; i--, j++) {
if (board[i][j] == 'Q') {
return false;
}
}
return true;
}
分治
分治算法是一种自顶向下的算法,它将问题分解成较小的子问题,递归地解决子问题,并合并子问题的解来得到最终解。分治算法特别适合解决具有递归结构的问题。
-
步骤:
- 将问题分解成较小的子问题。
- 递归地解决子问题。
- 合并子问题的解。
-
示例:归并排序
public int[] mergeSort(int[] arr) {
if (arr.length <= 1) {
return arr;
}
int mid = arr.length / 2;
int[] leftHalf = mergeSort(Arrays.copyOfRange(arr, 0, mid));
int[] rightHalf = mergeSort(Arrays.copyOfRange(arr, mid, arr.length));
return merge(leftHalf, rightHalf);
}
private int[] merge(int[] leftHalf, int[] rightHalf) {
int[] mergedArr = new int[leftHalf.length + rightHalf.length];
int leftIdx = 0;
int rightIdx = 0;
int mergedIdx = 0;
while (leftIdx < leftHalf.length && rightIdx < rightHalf.length) {
if (leftHalf[leftIdx] < rightHalf[rightIdx]) {
mergedArr[mergedIdx++] = leftHalf[leftIdx++];
} else {
mergedArr[mergedIdx++] = rightHalf[rightIdx++];
}
}
while (leftIdx < leftHalf.length) {
mergedArr[mergedIdx++] = leftHalf[leftIdx++];
}
while (rightIdx < rightHalf.length) {
mergedArr[mergedIdx++] = rightHalf[rightIdx++];
}
return mergedArr;
}
并查集
并查集是一种数据结构,它用于维护一组元素的集合。并查集支持三种主要操作:查找、合并和检查连通性。并查集特别适合解决连通性问题。
-
步骤:
- 创建一个数组,其中每个元素指向其父节点。
- 使用 find() 操作查找元素的根节点。
- 使用 union() 操作合并两个集合。
- 使用 connected() 操作检查两个元素是否属于同一集合。
-
示例:朋友圈问题
public class UnionFind {
private int[] parent; // 存储每个元素的父节点
private int[] size; // 存储每个集合的大小
public UnionFind(int n) {
parent = new int[n];
size = new int[n];
for (int i = 0; i < n; i++) {
parent[i] = i; // 初始化每个元素的父节点为自己
size[i] = 1; // 初始化每个集合的大小为 1
}
}
public int find(int x) {
if (parent[x] != x) {
parent[x] = find(parent[x]); // 递归查找根节点
}
return parent[x];
}
public