力扣hot100——图论

文章目录

51.岛屿的数量

题目描述

200. 岛屿数量

给你一个由 '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. 遍历网格
    • 遍历网格中的每一个节点。
    • 如果当前节点是陆地('1')且未被访问过,则说明发现了一个新的岛屿。
  2. 深度优先搜索(DFS)
    • 从当前陆地节点开始,使用 DFS 标记所有与之相连的陆地节点。
    • 在 DFS 过程中,访问上下左右四个方向的节点,确保所有相连的陆地都被标记为已访问。
  3. 计数岛屿
    • 每次发现一个新的岛屿时,计数器加 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.腐烂的橘子

题目描述

994. 腐烂的橘子

在给定的 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] 仅为 012

思路:BFS

  1. 广度优先搜索(BFS)
    • 使用 BFS 模拟腐烂过程。
    • 将所有初始腐烂橘子的位置加入队列。
    • 每分钟遍历队列中的所有腐烂橘子,将其相邻的新鲜橘子腐烂,并加入队列。
    • 记录腐烂的分钟数。
  2. 统计新鲜橘子数量
    • 在 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.课程表

题目描述

207. 课程表

你这个学期必须选修 numCourses 门课程,记为 0numCourses - 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] 中的所有课程对 互不相同

思路:拓扑排序

  1. 计算入度
    • 统计每个节点的入度(即有多少节点指向当前节点)。
  2. 拓扑排序
    • 每次取出入度为0的节点,并减少其相邻节点的入度。
  3. 判断是否有环
    • 如果所有节点都被处理过,则没有环;否则,存在环。

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(前缀树)

题目描述

208. 实现 Trie (前缀树)

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
  • wordprefix 仅由小写英文字母组成
  • insertsearchstartsWith 调用次数 总计 不超过 3 * 104

思路:前缀树/字典树

  1. 节点结构
    • 每个节点包含:
      • 一个长度为 26 的指针数组,表示 26 个小写字母。
      • 一个布尔值 isEnd,表示当前节点是否是一个单词的结尾。
  2. 插入操作
    • 从根节点开始,逐个字符插入。
    • 如果字符对应的子节点不存在,则创建新节点。
    • 插入完成后,标记最后一个节点的 isEndtrue
  3. 搜索操作
    • 从根节点开始,逐个字符查找。
    • 如果字符对应的子节点不存在,则返回 false
    • 如果所有字符都匹配,且最后一个节点的 isEndtrue,则返回 true
  4. 前缀匹配操作
    • 从根节点开始,逐个字符查找。
    • 如果字符对应的子节点不存在,则返回 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);
*/
相关推荐
蒟蒻小袁3 小时前
力扣面试150题-- 翻转二叉树
算法·leetcode·面试
养一只Trapped_beast3 小时前
【LeetCode】删除排序数组中的重复项 II
算法·leetcode·职场和发展
轮到我狗叫了4 小时前
力扣智慧思想小题,目录力扣.跳跃游戏(思想很重要)力扣.跳跃游戏II(还是思想)力扣.分发糖果力扣151.反转字符串中的单词力扣.轮转数组
数据结构·算法·leetcode
1白天的黑夜15 小时前
动态规划-62.不同路径-力扣(LeetCode)
c++·算法·leetcode·动态规划
有一个好名字6 小时前
力扣:轮转数组
数据结构·算法·leetcode
David Bates6 小时前
代码随想录第40天:图论1
python·算法·图论
秦少游在淮海7 小时前
leetcode - 双指针问题
算法·leetcode
多多*8 小时前
分布式ID设计 数据库主键自增
数据库·sql·算法·http·leetcode·oracle
SuperCandyXu9 小时前
leetcode0310. 最小高度树-medium
数据结构·c++·算法·leetcode
有一个好名字9 小时前
力扣:多数元素
算法·leetcode·职场和发展