👂 ▶ 怀抱的温柔并不属于我(弹唱版) (163.com)
👂 ▶ Gotta Have You (163.com)
👂 ▶ 心许百年 (163.com)
目录
🚩岛屿数量
200. 岛屿数量 - 力扣(LeetCode)
AC DFS
时间 O(mn):每个点遍历 1 次;空间 O(mn):所有点均为陆地,递归深度 m*n
class Solution {
public:
int ans;
int nex[4][2] = {{0,1},{1,0},{0,-1},{-1,0}}; // 4 个方向
// 从 x, y 开始深度优先搜索
void dfs(vector<vector<char>>& grid, int x, int y) {
int m = grid.size(), n = grid[0].size();
grid[x][y] = '0'; // 大水漫灌 / 感染法
for (int i = 0; i < 4; ++i) {
// 新的坐标
int xx = x + nex[i][0], yy = y + nex[i][1];
// 下一位置满足要求才递归,类似递归出口的处理
if (xx < m && yy < n && xx >= 0 && yy >= 0 && (grid[xx][yy] == '1') )
dfs(grid, xx, yy);
}
}
int numIslands(vector<vector<char>>& grid) {
int m = grid.size(), n = grid[0].size();
for (int i = 0; i < m; ++i)
for (int j = 0; j < n; ++j)
if (grid[i][j] == '1') {
ans++; // 岛屿数量 +1
dfs(grid, i, j);
}
return ans;
}
};
AC BFS
时间 O(mn),空间 O(min(m, n))
class Solution {
public:
int ans = 0; // 岛屿数量
int nex[4][2] = {{0,1},{0,-1},{1,0},{-1,0}}; // 4 个方向
int numIslands(vector<vector<char>>& grid) {
queue<pair<int, int>> q; // 横纵坐标
int m = grid.size(), n = grid[0].size();
// 对陆地上每个点 bfs
for (int i = 0; i < m; ++i)
for (int j = 0; j < n; ++j)
if (grid[i][j] == '1') { // 只对陆地 bfs
q.push({i, j}); // 入队
ans++;
grid[i][j] = '0'; // 大水漫灌 / 感染法
// 逐层扩展
while (!q.empty()) {
auto cur = q.front(); // 取队头
q.pop();
int x = cur.first, y = cur.second; // 取坐标
// 4 个方向扩展
for (int k = 0; k < 4; ++k) {
int xx = x + nex[k][0], yy = y + nex[k][1];
if (xx < 0 || yy < 0 || xx >= m || yy >= n)
continue; // 不是 break
if (grid[xx][yy] == '1') {
q.push({xx, yy});
grid[xx][yy] = '0'; // 感染法
}
}
}
}
return ans;
}
};
AC 并查集
解释
代码
class Solution {
public:
int dad[90000];
int count = 0; // 岛屿数量
int nex[4][2] = {{1,0},{-1,0},{0,1},{0,-1}}; // 4 个方向
// 找领头
int Find(int x) {
if (dad[x] == x)
return x;
else
return dad[x] = Find(dad[x]); // 路径压缩
}
// 合并两个团体
void join(int x, int y) {
int xx = Find(x), yy = Find(y);
if (xx != yy) {
dad[yy] = xx;
count--; // 合并陆地后,岛屿数量 -1
}
}
int numIslands(vector<vector<char>>& grid) {
int m = grid.size(), n = grid[0].size();
// dad.resize(m*n); // 声明大小
// 初始化 dad[]
for (int i = 0; i < m; ++i)
for (int j = 0; j < n; ++j)
if (grid[i][j] == '1') {
count++;
dad[n*i + j] = n*i + j; // 自己的祖先是自己
}
// 全图遍历
for (int i = 0; i < m; ++i)
for (int j = 0; j < n; ++j)
if (grid[i][j] == '1') {
grid[i][j] = '0'; // 防止多次合并同一块陆地
// 对 4 个方向进行合并
for (int k = 0; k < 4; ++k) {
// 新的坐标
int x = i + nex[k][0], y = j + nex[k][1];
if (x < 0 || y < 0 || x >= m || y >= n)
continue;
if (grid[x][y] == '1')
join(n*i + j, n*x + y); // 合并两点
}
}
return count;
}
};
🌼腐烂的橘子
994. 腐烂的橘子 - 力扣(LeetCode)
AC 多源bfs
时空 O(mn)
class Solution {
public:
int orangesRotting(vector<vector<int>>& grid) {
int nex[4][2] = {{1,0},{-1,0},{0,1},{0,-1}};
queue<pair<int, int>> q; // 存放坐标的队列
int fresh = 0; // 新鲜橘子数
int m = grid.size(), n = grid[0].size();
int min = 0;
// 初始化:得到新鲜橘子数 && 腐烂橘子加入队列
for (int i = 0; i < m; ++i)
for (int j = 0; j < n; ++j) {
if (grid[i][j] == 1)
fresh++;
if (grid[i][j] == 2)
q.push({i, j});
}
// 多源 bfs
while (!q.empty() && fresh) {
min++;
// 当前所有腐烂橘子,同时向 4 个方向扩散
for (int size = q.size(); size; --size) {
auto node = q.front(); // 队头
q.pop();
int x = node.first, y = node.second;
// 腐烂上下左右(4 个方向)的橘子
for (int i = 0; i < 4; ++i) {
int xx = x + nex[i][0], yy = y + nex[i][1];
// 越界
if (xx < 0 || yy < 0 || xx >= m || yy >= n)
continue;
// 新鲜的才能继续被感染
if (grid[xx][yy] == 1) {
grid[xx][yy] = 2;
fresh--; // 新鲜橘子 -1
q.push({xx, yy});
}
}
}
}
return fresh ? -1 : min;
}
};
🎂课程表
207. 课程表 - 力扣(LeetCode)
前置知识
AC BFS
思路
要素
坑
时间 O(m + n):初始化 = 边数 m(先修课程数) + 拓扑排序 = 节点数 n
空间 O(m + n):队列 n;邻接表 m + n,表头节点 n 个,边数 m
class Solution {
public:
// 这里的 prerequisites 先修课程,实际就是 图的点和边
bool canFinish(int numCourses, vector<vector<int>>& prerequisites) {
int count = numCourses; // 统计入度为 0 课程总数
// 初始化:邻接表 和 节点的入度
vector<vector<int>> v(numCourses);
vector<int> in(numCourses);
int n = prerequisites.size();
for (int i = 0; i < n; ++i) {
// y -> x,y 是 x 的先修课程
// 图:节点 y 指向 节点 x
int x = prerequisites[i][0], y = prerequisites[i][1];
v[y].push_back(x); // 邻接表加边
in[x]++; // 节点 x 入度 +1
}
// 入度为 0 的点加入队列 q
priority_queue<int, vector<int>, greater<int>> q; // 最小堆
for (int i = 0; i < numCourses; ++i)
if (in[i] == 0) {
q.push(i);
count--; // 统计是否有环
}
// 拓扑排序, bfs 逐层扩展
while (!q.empty()) {
int node = q.top(); // 取入度为 0 的最小节点 node
q.pop();
// 根据邻接表 v[] 在 in[] 删除该点所有出边
for (int i = 0; i < v[node].size(); ++i) {
int k = v[node][i]; // node -> k,k 的入度 -1
in[k]--;
if (in[k] == 0) { // 入度为 0
q.push(k);
count--;
}
}
}
return count ? false : true;
}
};
🌼实现 Trie(前缀树)
208. 实现 Trie (前缀树) - 力扣(LeetCode)
解释
坑
代码
m 个字母,插入,查询,前缀匹配,时间复杂度 都是 O(m)
字典树高度 n,最坏情况,不存在前缀相同的单词,空间复杂度 O(m^n)
class Trie {
private:
bool isEnd; // 结束标志位
vector<Trie*> children; // 下一前缀树节点
public:
Trie() : isEnd(false), children(26) {}
void insert(string word) {
Trie* node = this; // 相当于根节点开始
// 遍历单词,类似构造链表,没有该字母,就新建节点
for (auto x : word) {
int ch = x - 'a'; // 字母偏移量
if (node->children[ch] == nullptr)
node->children[ch] = new Trie();
node = node->children[ch]; // 移动下一节点
}
node->isEnd = true;
}
bool search(string word) {
Trie* node = this; // 相当于根节点开始
// 遍历单词
for (auto x : word) {
int ch = x - 'a';
if (node->children[ch] == nullptr)
return false;
node = node->children[ch]; // 移动到下一字母
}
return node->isEnd; // 单词是否结束
}
bool startsWith(string prefix) {
Trie* node = this; // 根节点
// 遍历前缀
for (auto x : prefix) {
int ch = x - 'a';
if (node->children[ch] == nullptr)
return false;
node = node->children[ch]; // 移动到下一字母
}
return true; // 前缀存在
}
};
/**
* Your Trie object will be instantiated and called as such:
* Trie* obj = new Trie();
* obj->insert(word);
* bool param_2 = obj->search(word);
* bool param_3 = obj->startsWith(prefix);
*/