文章目录
51.岛屿的数量
题目描述
给你一个由 '1'
(陆地)和 '0'
(水)组成的的二维网格,请你计算网格中岛屿的数量。
岛屿总是被水包围,并且每座岛屿只能由水平方向和/或竖直方向上相邻的陆地连接形成。
此外,你可以假设该网格的四条边均被水包围。
示例 1:
输入:grid = [
["1","1","1","1","0"],
["1","1","0","1","0"],
["1","1","0","0","0"],
["0","0","0","0","0"]
]
输出:1
示例 2:
输入:grid = [
["1","1","0","0","0"],
["1","1","0","0","0"],
["0","0","1","0","0"],
["0","0","0","1","1"]
]
输出:3
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 300
grid[i][j]
的值为'0'
或'1'
思路:DFS
用vis数组标记某个点是否已经被访问。通过DFS
访问上下左右四个方向的节点,并修改vis。
- 遍历网格 :
- 遍历网格中的每一个节点。
- 如果当前节点是陆地(
'1'
)且未被访问过,则说明发现了一个新的岛屿。
- 深度优先搜索(DFS) :
- 从当前陆地节点开始,使用 DFS 标记所有与之相连的陆地节点。
- 在 DFS 过程中,访问上下左右四个方向的节点,确保所有相连的陆地都被标记为已访问。
- 计数岛屿 :
- 每次发现一个新的岛屿时,计数器加 1。
- 最终返回岛屿的总数。
code
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
// 深度优先搜索(DFS)函数
void dfs(char** grid, int m, int n, bool** vis, int i, int j) {
// 如果当前节点超出边界、不是陆地或已访问过,则返回
if (i < 0 || i >= m || j < 0 || j >= n || grid[i][j] != '1' || vis[i][j]) {
return;
}
// 标记当前节点为已访问
vis[i][j] = true;
// 递归访问上下左右四个方向的节点
dfs(grid, m, n, vis, i - 1, j); // 上
dfs(grid, m, n, vis, i + 1, j); // 下
dfs(grid, m, n, vis, i, j - 1); // 左
dfs(grid, m, n, vis, i, j + 1); // 右
}
int numIslands(char** grid, int gridSize, int* gridColSize) {
if (gridSize == 0 || gridColSize == NULL) return 0;
int m = gridSize; // 网格的行数
int n = gridColSize[0]; // 网格的列数
int cnt = 0; // 岛屿数量
// 分配并初始化访问标记数组
bool** vis = (bool**)malloc(sizeof(bool*) * m);
for (int i = 0; i < m; i++) {
vis[i] = (bool*)malloc(sizeof(bool) * n);
memset(vis[i], false, sizeof(bool) * n); // 初始化每一行
}
// 遍历网格中的每个节点
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
// 如果当前节点是陆地且未访问过,则开始 DFS
if (grid[i][j] == '1' && !vis[i][j]) {
cnt++; // 岛屿数量加 1
dfs(grid, m, n, vis, i, j);
}
}
}
// 释放访问标记数组的内存
for (int i = 0; i < m; i++) {
free(vis[i]);
}
free(vis);
return cnt;
}
52.腐烂的橘子
题目描述
在给定的 m x n
网格 grid
中,每个单元格可以有以下三个值之一:
- 值
0
代表空单元格; - 值
1
代表新鲜橘子; - 值
2
代表腐烂的橘子。
每分钟,腐烂的橘子 周围 4 个方向上相邻 的新鲜橘子都会腐烂。
返回 直到单元格中没有新鲜橘子为止所必须经过的最小分钟数。如果不可能,返回 -1
。
示例 1:
输入:grid = [[2,1,1],[1,1,0],[0,1,1]]
输出:4
示例 2:
输入:grid = [[2,1,1],[0,1,1],[1,0,1]]
输出:-1
解释:左下角的橘子(第 2 行, 第 0 列)永远不会腐烂,因为腐烂只会发生在 4 个方向上。
示例 3:
输入:grid = [[0,2]]
输出:0
解释:因为 0 分钟时已经没有新鲜橘子了,所以答案就是 0 。
提示:
m == grid.length
n == grid[i].length
1 <= m, n <= 10
grid[i][j]
仅为0
、1
或2
思路:BFS
- 广度优先搜索(BFS) :
- 使用 BFS 模拟腐烂过程。
- 将所有初始腐烂橘子的位置加入队列。
- 每分钟遍历队列中的所有腐烂橘子,将其相邻的新鲜橘子腐烂,并加入队列。
- 记录腐烂的分钟数。
- 统计新鲜橘子数量 :
- 在 BFS 之前,统计网格中新鲜橘子的数量。
- 在 BFS 过程中,每腐烂一个新鲜橘子,新鲜橘子数量减 1。
- 如果 BFS 结束后仍有新鲜橘子,则返回
-1
。
code
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
// 定义方向数组:上下左右
int directions[4][2] = {{-1, 0}, {1, 0}, {0, -1}, {0, 1}};
int orangesRotting(int** grid, int gridSize, int* gridColSize) {
if (gridSize == 0 || gridColSize == NULL) return -1;
int m = gridSize; // 网格的行数
int n = gridColSize[0]; // 网格的列数
int fresh = 0; // 新鲜橘子的数量
int minutes = 0; // 腐烂所需的时间
// 定义队列
int* queue = (int*)malloc(sizeof(int) * m * n * 2); // 存储腐烂橘子的位置
int front = 0, tail = 0; // 队列的头尾指针
// 遍历网格,初始化队列和新鲜橘子数量
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if (grid[i][j] == 1) {
fresh++; // 统计新鲜橘子数量
} else if (grid[i][j] == 2) {
queue[tail++] = i; // 将腐烂橘子的行加入队列
queue[tail++] = j; // 将腐烂橘子的列加入队列
}
}
}
// 如果没有新鲜橘子,直接返回 0
if (fresh == 0) return 0;
// BFS 过程
while (front < tail) {
int size = tail - front; // 当前层的腐烂橘子数量
for (int i = 0; i < size; i += 2) {
int x = queue[front++]; // 取出腐烂橘子的行
int y = queue[front++]; // 取出腐烂橘子的列
// 遍历四个方向
for (int d = 0; d < 4; d++) {
int nx = x + directions[d][0];
int ny = y + directions[d][1];
// 如果相邻节点是新鲜橘子,则腐烂
if (nx >= 0 && nx < m && ny >= 0 && ny < n && grid[nx][ny] == 1) {
grid[nx][ny] = 2; // 标记为腐烂
fresh--; // 新鲜橘子数量减 1
queue[tail++] = nx; // 将新腐烂橘子的行加入队列
queue[tail++] = ny; // 将新腐烂橘子的列加入队列
}
}
}
if (tail > front) minutes++; // 如果队列不为空,时间加 1
}
// 释放队列内存
free(queue);
// 如果仍有新鲜橘子,返回 -1
return fresh == 0 ? minutes : -1;
}
53.课程表
题目描述
你这个学期必须选修 numCourses
门课程,记为 0
到 numCourses - 1
。
在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites
给出,其中 prerequisites[i] = [ai, bi]
,表示如果要学习课程 ai
则 必须 先学习课程 bi
。
- 例如,先修课程对
[0, 1]
表示:想要学习课程0
,你需要先完成课程1
。
请你判断是否可能完成所有课程的学习?如果可以,返回 true
;否则,返回 false
。
示例 1:
输入:numCourses = 2, prerequisites = [[1,0]]
输出:true
解释:总共有 2 门课程。学习课程 1 之前,你需要完成课程 0 。这是可能的。
示例 2:
输入:numCourses = 2, prerequisites = [[1,0],[0,1]]
输出:false
解释:总共有 2 门课程。学习课程 1 之前,你需要先完成课程 0 ;并且学习课程 0 之前,你还应先完成课程 1 。这是不可能的。
提示:
1 <= numCourses <= 2000
0 <= prerequisites.length <= 5000
prerequisites[i].length == 2
0 <= ai, bi < numCourses
prerequisites[i]
中的所有课程对 互不相同
思路:拓扑排序
- 计算入度 :
- 统计每个节点的入度(即有多少节点指向当前节点)。
- 拓扑排序 :
- 每次取出入度为0的节点,并减少其相邻节点的入度。
- 判断是否有环 :
- 如果所有节点都被处理过,则没有环;否则,存在环。
code Ⅰ
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
bool canFinish(int numCourses, int** prerequisites, int prerequisitesSize, int* prerequisitesColSize) {
// vis 数组:记录课程是否已经被处理过
bool vis[numCourses];
memset(vis, false, sizeof(vis)); // 初始化为 false
// indegree 数组:记录每个课程的入度
int indegree[numCourses];
memset(indegree, 0, sizeof(indegree)); // 初始化为 0
// 计算每个课程的入度
for (int i = 0; i < prerequisitesSize; i++) {
// prerequisites[i][0] 是当前课程,prerequisites[i][1] 是前置课程
// 当前课程的入度加 1
indegree[prerequisites[i][0]]++;
}
int cnt = 0; // 记录已经处理的课程数量
// 拓扑排序:每次选择一个入度为 0 的课程进行处理
while (cnt < numCourses) {
int course = -1; // 用于存储当前找到的入度为 0 的课程
// 遍历所有课程,找到一个入度为 0 且未被处理过的课程
for (int i = 0; i < numCourses; i++) {
if (indegree[i] == 0 && !vis[i]) {
course = i; // 找到符合条件的课程
vis[i] = true; // 标记为已处理
break;
}
}
// 如果没有找到入度为 0 的课程,说明存在环,返回 false
if (course == -1) {
return false;
}
// 修改相关课程的入度
for (int i = 0; i < prerequisitesSize; i++) {
// 如果当前课程是某个课程的前置课程
if (prerequisites[i][1] == course) {
// 相关课程的入度减 1
indegree[prerequisites[i][0]]--;
}
}
cnt++; // 已处理的课程数量加 1
}
// 如果所有课程都被处理过,说明没有环,返回 true
return true;
}
code Ⅱ(利用队列优化)
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
bool canFinish(int numCourses, int** prerequisites, int prerequisitesSize, int* prerequisitesColSize) {
// 初始化入度数组
int* indegree = (int*)malloc(sizeof(int) * numCourses);
memset(indegree, 0, sizeof(int) * numCourses);
// 计算每个课程的入度
for (int i = 0; i < prerequisitesSize; i++) {
indegree[prerequisites[i][0]]++;
}
// 使用队列存储入度为 0 的课程
int* queue = (int*)malloc(sizeof(int) * numCourses);
int front = 0, rear = 0;
// 将所有入度为 0 的课程加入队列
for (int i = 0; i < numCourses; i++) {
if (indegree[i] == 0) {
queue[rear++] = i;
}
}
int cnt = 0; // 记录已处理的课程数量
// 拓扑排序
while (front < rear) {
int course = queue[front++]; // 取出一个入度为 0 的课程
cnt++;
// 遍历所有边,更新相关课程的入度
for (int i = 0; i < prerequisitesSize; i++) {
if (prerequisites[i][1] == course) {
indegree[prerequisites[i][0]]--;
if (indegree[prerequisites[i][0]] == 0) {
queue[rear++] = prerequisites[i][0]; // 入度为 0 的课程加入队列
}
}
}
}
// 释放内存
free(indegree);
free(queue);
// 如果所有课程都被处理过,则没有环
return cnt == numCourses;
}
54.实现Ttie(前缀树)
题目描述
Trie (发音类似 "try")或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补全和拼写检查。
请你实现 Trie 类:
Trie()
初始化前缀树对象。void insert(String word)
向前缀树中插入字符串word
。boolean search(String word)
如果字符串word
在前缀树中,返回true
(即,在检索之前已经插入);否则,返回false
。boolean startsWith(String prefix)
如果之前已经插入的字符串word
的前缀之一为prefix
,返回true
;否则,返回false
。
示例:
输入
["Trie", "insert", "search", "search", "startsWith", "insert", "search"]
[[], ["apple"], ["apple"], ["app"], ["app"], ["app"], ["app"]]
输出
[null, null, true, false, true, null, true]
解释
Trie trie = new Trie();
trie.insert("apple");
trie.search("apple"); // 返回 True
trie.search("app"); // 返回 False
trie.startsWith("app"); // 返回 True
trie.insert("app");
trie.search("app"); // 返回 True
提示:
1 <= word.length, prefix.length <= 2000
word
和prefix
仅由小写英文字母组成insert
、search
和startsWith
调用次数 总计 不超过3 * 104
次
思路:前缀树/字典树
- 节点结构 :
- 每个节点包含:
- 一个长度为 26 的指针数组,表示 26 个小写字母。
- 一个布尔值
isEnd
,表示当前节点是否是一个单词的结尾。
- 每个节点包含:
- 插入操作 :
- 从根节点开始,逐个字符插入。
- 如果字符对应的子节点不存在,则创建新节点。
- 插入完成后,标记最后一个节点的
isEnd
为true
。
- 搜索操作 :
- 从根节点开始,逐个字符查找。
- 如果字符对应的子节点不存在,则返回
false
。 - 如果所有字符都匹配,且最后一个节点的
isEnd
为true
,则返回true
。
- 前缀匹配操作 :
- 从根节点开始,逐个字符查找。
- 如果字符对应的子节点不存在,则返回
false
。 - 如果所有字符都匹配,则返回
true
。
code
c
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#define ALPHABET_SIZE 26 // 字母表大小
// Trie 节点结构
typedef struct TrieNode {
struct TrieNode* children[ALPHABET_SIZE]; // 子节点指针数组
bool isEnd; // 标记是否是单词的结尾
} TrieNode;
// 创建新节点
TrieNode* createNode() {
TrieNode* node = (TrieNode*)malloc(sizeof(TrieNode));
if (node == NULL) {
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
node->isEnd = false;
for (int i = 0; i < ALPHABET_SIZE; i++) {
node->children[i] = NULL;
}
return node;
}
// Trie 结构
typedef struct {
TrieNode* root; // 根节点
} Trie;
// 初始化 Trie
Trie* trieCreate() {
Trie* trie = (Trie*)malloc(sizeof(Trie));
if (trie == NULL) {
fprintf(stderr, "Memory allocation failed\n");
exit(EXIT_FAILURE);
}
trie->root = createNode();
return trie;
}
// 插入单词
void trieInsert(Trie* obj, const char* word) {
if (obj == NULL || word == NULL || *word == '\0') return;
TrieNode* curr = obj->root;
for (int i = 0; word[i] != '\0'; i++) {
int index = word[i] - 'a'; // 计算字符对应的索引
if (curr->children[index] == NULL) {
curr->children[index] = createNode(); // 创建新节点
}
curr = curr->children[index]; // 移动到子节点
}
curr->isEnd = true; // 标记单词结尾
}
// 搜索单词
bool trieSearch(Trie* obj, const char* word) {
if (obj == NULL || word == NULL || *word == '\0') return false;
TrieNode* curr = obj->root;
for (int i = 0; word[i] != '\0'; i++) {
int index = word[i] - 'a';
if (curr->children[index] == NULL) {
return false; // 字符不存在
}
curr = curr->children[index]; // 移动到子节点
}
return curr->isEnd; // 检查是否是单词结尾
}
// 检查前缀
bool trieStartsWith(Trie* obj, const char* prefix) {
if (obj == NULL || prefix == NULL || *prefix == '\0') return false;
TrieNode* curr = obj->root;
for (int i = 0; prefix[i] != '\0'; i++) {
int index = prefix[i] - 'a';
if (curr->children[index] == NULL) {
return false; // 字符不存在
}
curr = curr->children[index]; // 移动到子节点
}
return true; // 前缀匹配成功
}
// 递归释放节点内存
void freeNode(TrieNode* node) {
if (node == NULL) return;
for (int i = 0; i < ALPHABET_SIZE; i++) {
freeNode(node->children[i]);
}
free(node);
}
// 释放 Trie 内存
void trieFree(Trie* obj) {
if (obj == NULL) return;
freeNode(obj->root); // 递归释放所有节点
free(obj); // 释放 Trie 结构
}
/**
* Your Trie struct will be instantiated and called as such:
* Trie* obj = trieCreate();
* trieInsert(obj, word);
* bool param_2 = trieSearch(obj, word);
* bool param_3 = trieStartsWith(obj, prefix);
* trieFree(obj);
*/