Flood Fill
染色问题
其实就是对图进行上下左右四个方向的dfs或者bfs,再加上一些条件判断并做一些操作。举例说明
Leetcode #733. 图像渲染:有一幅以二维整数数组表示的图画,每一个整数表示该图画的像素值大小,数值在 0 到 65535 之间。给你一个坐标 (sr, sc) 表示图像渲染开始的像素值(行 ,列)和一个新的颜色值 newColor,让你重新上色这幅图像。为了完成上色工作,从初始坐标开始,记录初始坐标的上下左右四个方向上像素值与初始坐标相同的相连像素点,接着再记录这四个方向上符合条件的像素点与他们对应四个方向上像素值与初始坐标相同的相连像素点,……,重复该过程。将所有有记录的像素点的颜色值改为新的颜色值。最后返回经过上色渲染后的图像。
示例 1:
输入:
image = [[1,1,1],
[1,1,0],
[1,0,1]]
sr = 1, sc = 1, newColor = 2
输出: [[2,2,2],
[2,2,0],
[2,0,1]]
解析:
在图像的正中间,(坐标(sr,sc)=(1,1)),
在路径上所有符合条件的像素点的颜色都被更改成2。
注意,右下角的像素没有更改为2,
因为它不是在上下左右四个方向上与初始点相连的像素点。
注意:
image 和 image[0] 的长度在范围 [1, 50] 内。
给出的初始点将满足 0 <= sr < image.length 和 0 <= sc < image[0].length。
image[i][j] 和 newColor 表示的颜色值在范围 [0, 65535]内。
解:
典型的染色问题,用dfs或bfs解决即可,养成习惯随手维护一个visited集合,避免重复搜索进入死循环。同时也要控制好索引边界,虽然这题说了不会越界。
1 class Solution: 2 def floodFill(self, image: List[List[int]], sr: int, sc: int, newColor: int) -> List[List[int]]: 3 if not image: 4 return [] 5 6 m, n = len(image), len(image[0]) 7 dx = [1, -1, 0, 0] 8 dy = [0, 0, 1, -1] 9 preColor = image[sr][sc] 10 visited = set() 11 12 def isValid(i, j): 13 nonlocal image, visited 14 if 0<=i<m and 0<=j<n and image[i][j] == preColor and (i, j) not in visited: 15 return True 16 return False 17 18 def dfs(i, j): 19 nonlocal image, newColor, visited 20 if not isValid(i, j): 21 return 22 image[i][j] = newColor 23 visited.add((i, j)) 24 for k in range(4): 25 dfs(i + dx[k], j + dy[k]) 26 27 dfs(sr, sc) 28 return image
尝试用bfs再来做一下,队列每次pop出来一个位置,判断是否需要染色,如果要染色的话就染色,然后搜索所有相邻位置,判断是否需要染色,需要就染,然后加入队列作为下一层的待遍历起始节点。
1 class Solution: 2 def floodFill(self, image: List[List[int]], sr: int, sc: int, newColor: int) -> List[List[int]]: 3 if not image: 4 return [] 5 6 m, n = len(image), len(image[0]) 7 dx = [1, -1, 0, 0] 8 dy = [0, 0, 1, -1] 9 preColor = image[sr][sc] 10 visited = set() 11 12 def isValid(i, j): 13 nonlocal image, visited 14 if 0<=i<m and 0<=j<n and image[i][j] == preColor and (i, j) not in visited: 15 return True 16 return False 17 18 def bfs(i, j): 19 nonlocal image, newColor, visited 20 21 image[i][j] = newColor # sr, sc 一定要染 22 visited.add((i, j)) 23 24 queue = [] 25 queue.append((i, j)) 26 27 while queue: 28 cur_i, cur_j = queue.pop(0) 29 for k in range(4): 30 new_i, new_j = cur_i+dx[k], cur_j+dy[k] 31 32 if isValid(new_i, new_j): # 如果相邻的要染,就染然后记录,然后作为下一层的起始点 33 image[new_i][new_j] = newColor 34 visited.add((new_i, new_j)) 35 queue.append((new_i, new_j)) 36 37 bfs(sr, sc) 38 return image
Leetcode # 1034. 边框着色:给出一个二维整数网格 grid,网格中的每个值表示该位置处的网格块的颜色。只有当两个网格块的颜色相同,而且在四个方向中任意一个方向上相邻时,它们属于同一连通分量。连通分量的边界是指连通分量中的所有与不在分量中的正方形相邻(四个方向上)的所有正方形,或者在网格的边界上(第一行/列或最后一行/列)的所有正方形。给出位于 (r0, c0) 的网格块和颜色 color,使用指定颜色 color 为所给网格块的连通分量的边界进行着色,并返回最终的网格 grid 。
解:
这题要求只给连通分量最外层染色,连通分量内部不需要染色的点的四面一定都是连通分量。所以就要判断这个点的四面有几个也是连通分量。难点就是在于如何设计这个判断条件,以及判断的先后顺序。
先判断越界没啥好说的,重点就是先判断是否遍历过还是先判断是否是其他颜色。考虑一种情况,是连通分量且已经遍历过了那颜色自然不是precolor,所以优先判断是否在 visited 集合中。
1 class Solution: 2 def colorBorder(self, grid: List[List[int]], r0: int, c0: int, color: int) -> List[List[int]]: 3 if not grid: 4 return [] 5 6 m, n = len(grid), len(grid[0]) 7 dx = [1, -1, 0, 0] 8 dy = [0, 0, 1, -1] 9 preColor = grid[r0][c0] 10 visited = set() 11 12 13 def dfs(i, j): 14 nonlocal grid, color, visited 15 if not (0<=i<m and 0<=j<n): # 如果越界,返回0,不计数 16 return 0 17 if (i, j) in visited: 18 return 1 # 是已经遍历过的点,说明和上一层递归函数中点相连,计数1 19 if grid[i][j] != preColor: 20 return 0 # 碰到其他颜色,返回0,不计数 21 22 # 以上情况都不是,遍历这个点 23 visited.add((i, j)) 24 25 surround = 0 26 for k in range(4): 27 surround += dfs(i + dx[k], j + dy[k]) 28 if surround < 4: 29 grid[i][j] = color # 如果四面都是连通分量,染色这个点 30 31 return 1 # 这个点是连通分量,返回1 32 33 34 dfs(r0, c0) 35 return grid
Leetcode #200. 岛计数问题:给定一个由 '1'(陆地)和 '0'(水)组成的的二维网格,计算岛屿的数量。一个岛被水包围,并且它是通过水平方向或垂直方向上相邻的陆地连接而成的。你可以假设网格的四个边均被水包围。
示例:
输入: 11000 11000 00100 00011 输出: 3
解:
转换为染色问题求解,把多余的1去掉不作计数
遍历所有节点
if node ==1:
count ++; 将node附近陆地节点赋0
else:
continue
问题,将node附近陆地节点赋0,如何实现?—— dfs / bfs / 并查集
dfs 实现,递归自然的提供栈
1 class Solution: 2 def numIslands(self, grid: List[List[str]]) -> int: 3 if not grid or not grid[0]: 4 return 0 5 self.dx = [-1, 1, 0, 0] 6 self.dy = [0, 0, -1, 1] # 便于向四周扩散,先定义好(dx, dy),左、右、下、上 7 8 self.max_x, self.max_y = len(grid), len(grid[0]) # 两个边界 9 10 self.grid = grid # 便于传参 11 self.visited = set() # 不污染原数组,用一个set做遍历标记 12 13 res = [] 14 for i in range(self.max_x): # 遍历所有节点,如果需要扩散染色就append 1,否则append 0 15 for j in range(self.max_y): 16 res.append(self.floodfill_dfs(i,j)) 17 18 return sum(res) # sum后即为所求 19 20 def floodfill_dfs(self, x, y): 21 if not self.is_valid(x, y): # 如果(x, y)不是需要扩散染色的陆地,直接返回0 22 return 0 23 self.visited.add((x, y)) 24 for k in range(4): # 左、右、下、上4个方向扩展遍历 25 self.floodfill_dfs(x + self.dx[k], y + self.dy[k]) 26 return 1 27 28 def is_valid(self, x, y): 29 """判断(x, y)是否为需要扩散染色的陆地""" 30 if x < 0 or x >= self.max_x or y < 0 or y >= self.max_y: # 越界的位置全是水 31 return False 32 if self.grid[x][y] == '0' or ((x, y) in self.visited): # ‘0’ node 和 已经染色过的‘1’ node,都不需要染色,不破坏原数组grid 33 return False 34 return True
bfs实现,利用队列
1 import collections 2 class Solution: 3 def numIslands(self, grid: List[List[str]]) -> int: 4 if not grid or not grid[0]: 5 return 0 6 self.dx = [-1, 1, 0, 0] 7 self.dy = [0, 0, -1, 1] # 便于向四周扩散,先定义好(dx, dy),左、右、下、上 8 9 self.max_x, self.max_y = len(grid), len(grid[0]) # 两个边界 10 11 self.grid = grid # 便于传参 12 self.visited = set() # 不污染原数组,用一个set做遍历标记 13 14 res = [] 15 for i in range(self.max_x): # 遍历所有节点,如果需要扩散染色就append 1,否则append 0 16 for j in range(self.max_y): 17 res.append(self.floodfill_bfs(i,j)) 18 19 return sum(res) # sum后即为所求 20 21 def floodfill_bfs(self, x, y): 22 if not self.is_valid(x, y): # 如果(x, y)不是需要扩散染色的陆地,直接返回0 23 return 0 24 25 self.visited.add((x, y)) 26 queue = collections.deque() # bfs用队列实现 27 queue.append((x,y)) # 先入队遍历的起始点 28 29 while queue: 30 cur_x, cur_y = queue.popleft() 31 for k in range(4): # 所有和当前pop出来的节点相邻的点都要染色(前提是如果需要的话) 32 new_x, new_y = cur_x+self.dx[k], cur_y+self.dy[k] 33 if self.is_valid(new_x, new_y): 34 self.visited.add((new_x, new_y)) # 相当于染色(new_x, new_y) 35 queue.append((new_x, new_y)) # 相邻节点入队 36 return 1 37 38 def is_valid(self, x, y): 39 """判断(x, y)是否为需要扩散染色的陆地""" 40 if x < 0 or x >= self.max_x or y < 0 or y >= self.max_y: # 越界的位置全是水 41 return False 42 if self.grid[x][y] == '0' or ((x, y) in self.visited): # ‘0’ node 和 已经染色过的‘1’ node,都不需要染色,不破坏原数组grid 43 return False 44 return True