1. 递归
定义描述
循环直接或间接地调用小问题解决方案,直到解决同类大问题。
解题策略
- 确定输入输出;
- 确定递归条件;
- 确定递归调用关系。
C++举例--翻转链表
代码:
class Solution {
public:
ListNode* reverseList(ListNode* head) {
ListNode* cur = NULL, *pre = head;
while (pre != NULL) {
ListNode* t = pre->next;
pre->next = cur;
cur = pre;
pre = t;
}
return cur;
}
};
分析:
- 定义两个指针: pre 和 cur ;pre 在前 cur 在后。
- 每次让 pre 的 next 指向 cur ,实现一次局部反转
- 局部反转完成之后,pre 和 cur 同时往前移动一个位置
- 循环上述过程,直至 pre 到达链表尾部
2. 分治
定义描述
顾名思义,分而治之;将复杂大问题分成两个或多个相似小问题,各个子问题相互独立,先求解小问题,最后求解大问题。
解题策略
- 分解复杂大问题。各个子问题与大问题类似且相互独立;
- 求解各个子问题;
- 合并求解。将各个子问题的解合并为大问题的解。
3. 贪心
定义描述
大问题复杂度过高,则分解为各个子问题,每个子问题寻找最优解,合并为全局解,全局解不一定最优。
解题策略
- 确定最优解;
- 分解大问题,每个子问题都要满足最优解;
- 合并所有子问题最优解为全局解,全局解不一定最优。
C++举例--最小生成树
代码:
class Djset {
public:
vector<int> parent; // 记录节点的根
vector<int> rank; // 记录根节点的深度(用于优化)
vector<int> size; // 记录每个连通分量的节点个数
vector<int> len; // 记录每个连通分量里的所有边长度
int num; // 记录节点个数
Djset(int n): parent(n), rank(n), len(n, 0), size(n, 1), num(n) {
for (int i = 0; i < n; i++) {
parent[i] = i;
}
}
int find(int x) {
// 压缩方式:直接指向根节点
if (x != parent[x]) {
parent[x] = find(parent[x]);
}
return parent[x];
}
int merge(int x, int y, int length) {
int rootx = find(x);
int rooty = find(y);
if (rootx != rooty) {
if (rank[rootx] < rank[rooty]) {
swap(rootx, rooty);
}
parent[rooty] = rootx;
if (rank[rootx] == rank[rooty]) rank[rootx] += 1;
// rooty的父节点设置为rootx,同时将rooty的节点数和边长度累加到rootx,
size[rootx] += size[rooty];
len[rootx] += len[rooty] + length;
// 如果某个连通分量的节点数 包含了所有节点,直接返回边长度
if (size[rootx] == num) return len[rootx];
}
return -1;
}
};
struct Edge {
int start; // 顶点1
int end; // 顶点2
int len; // 长度
};
class Solution {
public:
int minCostConnectPoints(vector<vector<int>>& points) {
int res = 0;
int n = points.size();
Djset ds(n);
vector<Edge> edges;
// 建立点-边式数据结构
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
Edge edge = {i, j, abs(points[i][0] - points[j][0]) + abs(points[i][1] - points[j][1])};
edges.emplace_back(edge);
}
}
// 按边长度排序
sort(edges.begin(), edges.end(), [](const auto& a, const auto& b) {
return a.len < b.len;
});
// 连通分量合并
for (auto& e : edges) {
res = ds.merge(e.start, e.end, e.len);
if (res != -1) return res;
}
return 0;
}
};
定义:
代码:
4. 回溯
定义描述
针对每一步搜索试探解决方案,如果不满足期望结果,则回溯回去重新选择。
解题策略
- 确定解空间;
- 确定探索方法;
- 在解空间探索,不满足期望结果,则回溯回去重新选择解方法。
C++举例--深度优先搜索
代码:
class Solution {
private:
void dfs(vector<vector<char>>& grid, int r, int c) {
int nr = grid.size();
int nc = grid[0].size();
grid[r][c] = '0';
if (r - 1 >= 0 && grid[r-1][c] == '1') dfs(grid, r - 1, c);
if (r + 1 < nr && grid[r+1][c] == '1') dfs(grid, r + 1, c);
if (c - 1 >= 0 && grid[r][c-1] == '1') dfs(grid, r, c - 1);
if (c + 1 < nc && grid[r][c+1] == '1') dfs(grid, r, c + 1);
}
public:
int numIslands(vector<vector<char>>& grid) {
int nr = grid.size();
if (!nr) return 0;
int nc = grid[0].size();
int num_islands = 0;
for (int r = 0; r < nr; ++r) {
for (int c = 0; c < nc; ++c) {
if (grid[r][c] == '1') {
++num_islands;
dfs(grid, r, c);
}
}
}
return num_islands;
}
};
5. 枚举
定义描述
将问题所有可能的答案枚举,根据条件判断是否合适,合适则保留,否则丢弃。
解题策略
- 确定枚举对象、枚举范围、判断条件;
- 枚举可能的解,验证每个解是否合适。
c++举例--计数质数
代码:
class Solution {
public:
int countPrimes(int n) {
if(n < 3)
return 0;;
//从3开始验算,所以初始值为1(2为质数)。
int count = 1;
for (int i = 3; i < n; i++){
//当某个数为 2 的 n 次方时(n为自然数),其 & (n - 1) 所得值将等价于取余运算所得值
//*如果 x = 2^n ,则 x & (n - 1) == x % n
//if(i % 2 == 0)
if ((i & 1) == 0)
continue; ;
bool sign = true;
//用 j * j <= i 代替 j <= √i 会更好。
//因为我们已经排除了所有偶数,所以每次循环加二将规避偶数会减少循环次数
for (int j = 3; j * j <=i; j+=2){
if (i % j == 0){
sign = false;
break;
}
}
if (sign)
count++; ;
}
return count;
}
}