忏悔

今天因台风停课通知冲击大脑, 懈怠了, 我有罪

热题 100 题

岛屿数量

入门dfs, bfs, 太久没写了, 康复训练

# 丑陋的一版 dfs
class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        g = [(1, 0), (-1, 0), (0, -1), (0, 1)] # 方便遍历移动可能
        ans = 0

        def dfs(x, y):
            # 终止条件
            if grid[x][y] == '0':
                return
            else:
                # 防止重复遍历, 把走过的点设为 '0'
                grid[x][y] = '0'
                # 遍历四个方位
                for x_nxt, y_nxt in g:
                    x1, y1 = x + x_nxt, y + y_nxt
				  # 防止越界
                    if 0 <= x1 < len(grid) and 0 <= y1 < len(grid[0]):
                        dfs(x1, y1)

        # 遍历
        for x in range(len(grid)):
            for y in range(len(grid[0])):
                if grid[x][y] == '1':
                    dfs(x, y)
                    ans += 1

        return ans

# 稍微改进一下逻辑和代码简洁度
class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        g = [(1, 0), (-1, 0), (0, -1), (0, 1)]
        n, m = len(grid), len(grid[0])
        ans = 0

        def dfs(i, j):
            grid[i][j] = '0'

            for dx, dy in g:
                x, y = i + dx, j + dy
                # 有个两个非常简单的判断 x 小于 n 还是 m, 一是看的遍历时候的边界, 二是看[x][y], 还是[y][x]
                if 0 <= x < n and 0 <= y < m and grid[x][y] == '1':
                    dfs(x, y)
    
        # 是的就是这里
        for i in range(n):
            for j in range(m):
                if grid[i][j] == '1':
                    ans += 1
                    dfs(i, j)   

        return ans
  
# bfs 版本
class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        n, m = len(grid), len(grid[0])
        d = [(1, 0), (0, 1), (-1, 0), (0, -1)]
        ans = 0
        q = deque()

        for i in range(n):
            for j in range(m):
                if grid[i][j] == '1':
                    ans += 1
                    # 标记为已访问
                    grid[i][j] = '0'
                    q.append((i, j))

                    while q:
                        x, y = q.popleft()
                        for dx, dy in d:
                            x1, y1 = x + dx, y + dy

                            if 0 <= x1 < n and 0 <= y1 < m and grid[x1][y1] == '1':
                                # 因为还在循环找下一层, 我们得入队就设置为 0, 防止多次入队
                                grid[x1][y1] = '0'
                                q.append((x1, y1))

        return ans

腐烂橘子

非常非常经典的多源bfs计数, 同样类似的还有岛屿着火等等, 我给出我的两种写法, 一种按照bfs层数计数, 一种使用带有时间戳的队列计数

# 记录循环层写法
class Solution:
    def orangesRotting(self, grid: List[List[int]]) -> int:
        d = [(1, 0), (0, 1), (-1, 0), (0, -1)]
        n, m = len(grid), len(grid[0])
        cur = time = 0
        q = deque()
    
        for i in range(n):
            for j in range(m):
                if grid[i][j] == 1:
                    cur += 1
                elif grid[i][j] == 2:
                    q.append((i, j))
    
        if cur == 0:
            return 0
		# 避免没有新鲜橘子时队列没有空导致结果多 1, bfs前序检验现在的新鲜橘子数量
        while q and cur:
            size = len(q) # 不会随着当前 q 的改变而改变
            for _ in range(size):
                x, y = q.popleft()
                for dx, dy in d:
                    nx, ny = x + dx, y + dy

                    if 0 <= nx < n and 0 <= ny < m and grid[nx][ny] == 1:
                        grid[nx][ny] = 2
                        cur -= 1
                        q.append((nx, ny))
        
            time += 1
    
        return -1 if cur else time
# 时间戳写法
class Solution:
    def orangesRotting(self, grid: List[List[int]]) -> int:
        d = [(1, 0), (0, 1), (-1, 0), (0, -1)]
        n, m = len(grid), len(grid[0])
        cur = time = 0
        q = deque()
    
        for i in range(n):
            for j in range(m):
                if grid[i][j] == 1:
                    cur += 1
                elif grid[i][j] == 2:
                    q.append((i, j, 0)) # 引入一个腐烂时间戳
    
        if cur == 0:
            return 0
                
        while q and cur:
            x, y, t = q.popleft()
        
            for dx, dy in d:
                nx, ny = x + dx, y + dy

                if 0 <= nx < n and 0 <= ny < m and grid[nx][ny] == 1:
                    grid[nx][ny] = 2
                    cur -= 1
                    q.append((nx, ny, t + 1))
                    time = t + 1
    
        return -1 if cur else time

课程表

初看就是一个图内成环问题, 使用了并查集, 然后发现不对, 这是个拓扑排序, 需要判断的是有没有形成循环(贪吃蛇), 并不是单纯的成环就false

# 初版(错误的版本!!!)
class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        # 课程从 0 开始, 非常友好
        fa = [i for i in range(numCourses)]
		# 路径压缩的查找
        def find(x):
            if fa[x] != x:
                fa[x] = find(fa[x])

            return fa[x]

        # 其实这里可以做一个小树合到大树的优化, 但是这题数据量不大, 就没必要了
        def union(u, v):
            if find(u) != find(v):
                fa[find(u)] = find(v)
                return True

            return False
  
        for u, v in prerequisites:
            if not union(u, v):
                return False
  
        return True
# 三色标记法dfs
# 0 代表尚未学习. 1 代表学习中(递归栈), 2 代表学习完了
class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        if not prerequisites:
            return True

        grid = [[] for _ in range(numCourses)]

        for u, v in prerequisites:
            grid[v].append(u)
		# 刚开始都没有学习
        check = [0] * numCourses

        def dfs(cur):
            # 递归到了学习中的课程, 也就成环了(还没学完就被拉去当前置条件)
            if check[cur] == 1:
                return False
            # 已经学习完了, 可以跳出递归
            if check[cur] == 2:
                return True
            # 标记当前学习着的课程
            check[cur] = 1
            for v in grid[cur]:
                # 前置课程无法正常学完, 遗憾败北
                if not dfs(v):
                    return False
            # 递归结束, 学完了前置课程, 也就可以学完当前课程了
            check[cur] = 2
            return True

        for i in range(numCourses):
            if not dfs(i):
                return False

        return True
  
# 入度角度的 bfs
# 我们可以这样思考, 从入度为 0 的节点开始往后删除, 连带着边一起删除, 就像一层一层的往里推进, 如果存在循环, 循环圈永远不可能入度为 0, 不会被删除, 所以我们只要比较删除的节点数和总课程数是否相等就好
class Solution:
    def canFinish(self, numCourses: int, prerequisites: List[List[int]]) -> bool:
        grid = [[] for _ in range(numCourses)]
        cnt = [0] * numCourses
        # v是前置课程, 也就是 v -> u, 正向建图
        for u, v in prerequisites:
            grid[v].append(u)
            cnt[u] += 1
  
        # 把第一层的节点放入初始化队列
        q = deque([i for i in range(numCourses) if cnt[i] == 0])
        ans = 0 # 删除的节点数

        while q:
            u = q.popleft()
            ans += 1
            # 删除掉所有出边
            for v in grid[u]:
                cnt[v] -= 1
                # 出现新的 0 入度节点, 火速加入生死簿
                if cnt[v] == 0:
                    q.append(v)
  
        return ans == numCourses
# 还有出度角度的逆向做法,只是建图反着建就好, 其他没有太大区别

贴一张图(学艺不精, 不会做动图), 如果没有环就会一层一层删除掉所有节点

微信图片_20250922234603_92_169.jpg

468 周赛

第一题.偶数的按位或运算

数据量很小, 直接线性遍历就好(时间O(n), 空间O(1))

class Solution:
    def evenNumberBitwiseORs(self, nums: List[int]) -> int:
        ans = 0
        for i in nums:
            if i % 2 == 0:
                ans |= i
  
        return ans 

第二题.最大字数组总值(一)

难以理解这玩意为什么示例这么抽象, 题目明确

同一个子数组(相同的 lr可以 被选择超过一次。

直接 最大最小的差 * k 就好了, 难点(竟然是中等题)在于示例吓唬人?

class Solution:
    def maxTotalValue(self, nums: List[int], k: int) -> int:
        return k * (max(nums) - min(nums))

一天是牛马, 一辈子是牛马