当前位置: 首页 > news >正文

力扣hot100 | 图论 | 200. 岛屿数量、994. 腐烂的橘子、207. 课程表、208. 实现 Trie (前缀树)

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

把访问过的岛屿插满旗子——避免重复访问和无限递归

题解参见:灵茶山艾府

  • 【思路】
class Solution:def numIslands(self, grid: List[List[str]]) -> int:m, n = len(grid), len(grid[0]) # 不用修改,所以不用在dfs中nonlocal声明def dfs(i: int, j: int) -> None:# 出界,或者不是 '1',就不再往下递归if i < 0 or i >= m or j < 0 or j >= n or grid[i][j] != '1':returngrid[i][j] = '2' # 插旗!避免来回横跳无限递归dfs(i, j - 1) # 向左dfs(i, j + 1) # 向右dfs(i - 1, j) # 向上dfs(i + 1, j) # 向下res = 0for i, row in enumerate(grid):for j, val in enumerate(row):if val == '1':dfs(i, j)  # 把此元素val所在岛屿上插满旗子,这样后面遍历到的 '1' 一定是新的岛res += 1return res
  • 时间复杂度 O(mn)
  • 空间复杂度 O(mn)

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 。

BFS

以下题解搬运自:灵茶山艾府

看示例 1:
在这里插入图片描述

  1. 统计所有初始就腐烂的橘子的位置,加到列表 q 中,现在 q=[(0,0)]。
  2. 初始化答案 ans=0。模拟橘子腐烂的过程,不断循环,直到没有新鲜橘子,或者 q 为空。
  3. 答案加一,在第 ans=1 分钟,遍历 q 中橘子的四方向相邻的新鲜橘子,把这些橘子腐烂,q 更新为这些橘子的位置,现在 q=[(0,1),(1,0)]。
  4. 答案加一,在第 ans=2 分钟,遍历 q 中橘子的四方向相邻的新鲜橘子,把这些橘子腐烂,q 更新为这些橘子的位置,现在 q=[(0,2),(1,1)]。
  5. 答案加一,在第 ans=3 分钟,遍历 q 中橘子的四方向相邻的新鲜橘子,把这些橘子腐烂,q 更新为这些橘子的位置,现在 q=[(2,1)]。
  6. 答案加一,在第 ans=4 分钟,遍历 q 中橘子的四方向相邻的新鲜橘子,把这些橘子腐烂,q 更新为这些橘子的位置,现在 q=[(2,2)]。
  7. 由于没有新鲜橘子,退出循环。
    为了判断是否有永远不会腐烂的橘子(如示例 2),我们可以统计初始新鲜橘子的个数 fresh。在 BFS 中,每有一个新鲜橘子被腐烂,就把 fresh 减一,这样最后如果发现 fresh>0,就意味着有橘子永远不会腐烂,返回 −1

代码实现时,在 BFS 中要将 grid[i][j]=1 的橘子修改成 2(或者其它不等于 1 的数),这可以保证每个橘子加入 q 中至多一次。如果不修改,我们就无法知道哪些橘子被腐烂过了,比如示例 1 中 (0,1) 去腐烂 (1,1),而 (1,1) 在此之后又重新腐烂 (0,1),如此反复,程序就会陷入死循环。读者可以注释掉下面代码中的 grid[i][j] = 2 这行代码试试。

  • 【步骤】
    • 第一步:初始化
      1. fresh 统计新鲜橘子的总数
      2. 用列表q统计所有腐烂的橘子位置
      3. 初始化时间计数器 res = 0
    • 第二步:BFS遍历
      1. 外层循环:当队列不为空且还有新鲜橘子时继续
      2. 时间递增:每轮循环开始时 res+= 1,代表过了一分钟
      3. 当前层处理:
        • 保存当前队列中的所有腐烂橘子(代表这一分钟开始时已经腐烂的橘子)
        • 清空队列,准备存放下一分钟新腐烂的橘子
      4. 四方向扩散:
        • 对每个当前腐烂的橘子,检查其四个相邻位置
        • 如果相邻位置是新鲜橘子,则:
          • 将其标记为腐烂(grid[i][j] = 2
          • 新鲜橘子数量减一(fresh -= 1
          • 将新腐烂的橘子加入队列
    • 第三步:返回结果
      • 如果还有新鲜橘子剩余,说明有橘子永远不会腐烂,返回 -1
      • 否则返回所用的时间 res
class Solution:def orangesRotting(self, grid: List[List[int]]) -> int:m, n = len(grid), len(grid[0])fresh = 0q = []# 初始状态统计for i, row in enumerate(grid):for j, val in enumerate(row):if val == 1:fresh += 1  # 统计新鲜橘子个数elif val == 2:q.append((i, j)) # 一开始就腐烂的橘子res = 0while q and fresh:  # 开始计时res += 1        # 每轮时间加一tmp = qq = []  # 清空上一轮的腐烂位置,不然腐烂过的位置会重新腐烂for x, y in tmp:for i, j in (x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1): # 本轮腐烂范围if 0 <= i < m and 0 <= j < n and grid[i][j] == 1: # 若是新鲜橘子fresh -= 1                                   # 就使其腐烂grid[i][j] = 2q.append((i, j))return -1 if fresh else res
  • 时间复杂度 O(mn)
  • 空间复杂度 O(mn)

207. 课程表

力扣题目链接

你这个学期必须选修 numCourses 门课程,记为 0numCourses - 1

在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出,其中 prerequisites[i] = [ai, bi] ,表示如果要学习课程 a_i则 必须 先学习课程 b_i

例如,先修课程对 [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 。这是不可能的。

三色标记法

此题解搬运自:灵茶山艾府

  • 【题意】给你一个有向图,判断图中是否有环。(把每个 prerequisites[i]=[a,b] 看成一条有向边 b→a,构建一个有向图 g:若图中有环,则说明不可能完成任务(“互为先修课”))。
  • 【如何找环?】例如下图 1→2→3→4→2,走到 4 的时候,发现下一个节点 2 在递归栈中(“正在访问中”),那么就找到了环。
    在这里插入图片描述
  • 【三色标记法】对于每个节点 x,都定义三种颜色值(状态值):
    • 0:节点 x 尚未被访问到。
    • 1:节点 x 正在访问中,dfs(x) 尚未结束。
    • 2:节点 x 已经完全访问完毕,dfs(x) 已返回。
    • 【⚠误区】不能只用两种状态表示节点「没有访问过」和「访问过」。例如上图,我们先 dfs(0),再 dfs(1),此时 1 的邻居 0 已经访问过,但这并不能表示此时就找到了环。
  • 【步骤】
    1. 建图:把每个 prerequisites[i]=[a,b] 看成一条有向边 b→a,构建一个有向图 g
    2. 创建长为 numCourses 的颜色数组 colors,所有元素值初始化成 0
    3. 遍历 colors,如果 colors[i]=0,则调用递归函数 dfs(i)
    4. 执行 dfs(x)
      • 首先标记 colors[x]=1,表示节点 x 正在访问中。
      • 然后遍历 x 的邻居 y。如果 colors[y]=1,则找到环,返回 true。如果 colors[y]=0(没有访问过)且 dfs(y) 返回了 true,那么 dfs(x) 也返回 true
      • 如果没有找到环,那么先标记 colors[x]=2,表示 x 已经完全访问完毕,然后返回 false
    5. 如果 dfs(i) 返回 true,那么找到了环,返回 false
    6. 如果遍历完所有节点也没有找到环,返回 true
class Solution:def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:g = [[] for _ in range(numCourses)]for a, b in prerequisites:g[b].append(a)colors = [0] * numCourses# 返回 True 表示找到了环def dfs(x: int) -> bool:colors[x] = 1  # x 正在访问中for y in g[x]:if colors[y] == 1 or colors[y] == 0 and dfs(y):return True  # 找到了环colors[x] = 2  # x 完全访问完毕return False  # 没有找到环for i, c in enumerate(colors):if c == 0 and dfs(i):return False  # 有环return True  # 没有环
  • 时间复杂度 O(n+m),其中 n 是 numCourses,m 是 prerequisites 的长度。每个节点至多递归访问一次,每条边至多遍历一次。
  • 空间复杂度 O(n+m)。存储 g 需要 O(n+m) 的空间。

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
  • word 和 prefix 仅由小写英文字母组成
  • insert、search 和 startsWith 调用次数 总计 不超过 3 * 104 次

从二叉树到26叉树

以下题解参见:灵茶山艾府

  • 【思路】待补充
class Node:__slots__ = 'son', 'end'def __init__(self):self.son = {}self.end = Falseclass Trie:def __init__(self):self.root = Node()def insert(self, word: str) -> None:cur = self.rootfor c in word:if c not in cur.son:  # 无路可走?cur.son[c] = Node()  # 那就造路!cur = cur.son[c]cur.end = Truedef find(self, word: str) -> int:cur = self.rootfor c in word:if c not in cur.son:  # 道不同,不相为谋return 0cur = cur.son[c]# 走过同样的路(2=完全匹配,1=前缀匹配)return 2 if cur.end else 1def search(self, word: str) -> bool:return self.find(word) == 2def startsWith(self, prefix: str) -> bool:return self.find(prefix) != 0
  • 时间复杂度:初始化为 O(1),insert 为 O(n∣Σ∣),其余为 O(n),其中 nword 的长度,∣Σ∣=26 是字符集合的大小。注意创建一个节点需要 O(∣Σ∣) 的时间(如果用的是数组)。
  • 空间复杂度:O(qn∣Σ∣)。其中 qinsert 的调用次数。
http://www.dtcms.com/a/347402.html

相关文章:

  • 【数据分享】2025年全国路网矢量数据道路shp数据
  • Ubuntu 系统中解压 ZIP 文件可以通过图形界面或命令行操作
  • 【设计模式08】组合模式
  • LLaMA-Factory 中配置文件或命令行里各个参数的含义
  • 深度集成Dify API:基于Vue 3的智能对话前端解决方案
  • Maven仓库与Maven私服架构
  • Vue 3 useModel vs defineModel:选择正确的双向绑定方案
  • 自然语言处理——05 Transformer架构和手写实现
  • 微前端架构核心要点对比
  • 小区物业对大楼顶面的巡查通常是定期巡查+特殊情况下的临时巡查相结合
  • 认识模块化及常见考点
  • 【秋招笔试】2025.08.23京东秋招笔试题
  • onnx入门教程(二)—— PyTorch 转 ONNX 详解
  • C#多线程同步利器:Monitor全解析
  • 安卓10.0系统修改定制化____如何修改固件 去除开机后默认的屏幕锁定
  • AcWing 114. 【0x07】国王游戏
  • C代码学习笔记(一)
  • Windows打开命令窗口的几种方式
  • 使用 PSRP 通过 SSH 建立 WinRM 隧道
  • 注意力机制中为什么q与k^T相乘是注意力分数
  • 每日定投40刀BTC(22)20250802 - 20250823
  • 编程刷题-染色题DFS
  • 03_数据结构
  • 在 CentOS 7 上搭建 OpenTenBase 集群:从源码到生产环境的全流程指南
  • MSPM0G3507工程模板创建
  • 微信小程序自定义组件开发(上):从创建到数据通信详解(五)
  • 纠删码技术,更省钱的分布式系统的可靠性技术
  • 使用springboot开发-AI智能体平台管理系统,统一管理各个平台的智能体并让智能体和AI语音设备通信,做一个属于自己的小艾同学~
  • Dubbo vs Feign
  • 个人思考与发展