面试题 08.10. 颜色填充
面试题 16.19. 水域大小
130. 被围绕的区域
1219. 黄金矿工
79. 单词搜索
编写函数,实现许多图片编辑软件都支持的「颜色填充」功能。
待填充的图像用二维数组 image 表示,元素为初始颜色值。初始坐标点的横坐标为 sr 纵坐标为 sc。需要填充的新颜色为 newColor 。
「周围区域」是指颜色相同且在上、下、左、右四个方向上存在相连情况的若干元素。
请用新颜色填充初始坐标点的周围区域,并返回填充后的图像。
示例:输入:
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 ,因为它不属于初始坐标点的周围区域。
这个是一个非常简单的深度优先搜索问题,其思想就是,从给定的点出发,遍历上下左右四个方向上与该点像素相同的没有遍历过的点(这也是代码中if的语句的判定条件),然后将这些点的像素变为新值。
dp表示能否访问,True表示可以访问。
def floodFill(self, image, sr, sc, newColor):
m,n = len(image),len(image[0])
oldcolor = image[sr][sc]
def dfs(i,j):
if i < 0 or j < 0 or i >=m or j >=n:
return
if image[i][j] == oldcolor and dp[i][j]:
dp[i][j] = False #已经访问到,置为为False
image[i][j] = newColor
dfs(i-1,j) #up
dfs(i+1,j) #down
dfs(i,j-1) #left
dfs(i,j+1) #right
dp = [[True for i in range(n)] for j in range(m)] #dp表示能否访问,True表示可以访问
dfs(sr,sc)
return image
你有一个用于表示一片土地的整数矩阵land,该矩阵中每个点的值代表对应地点的海拔高度。若值为0则表示水域。由垂直、水平或对角连接的水域为池塘。池塘的大小是指相连接的水域的个数。编写一个方法来计算矩阵中所有池塘的大小,返回值需要从小到大排序。
示例:输入:
[
[0,2,1,0],
[0,1,0,1],
[1,1,0,1],
[0,1,0,1]
]
输出: [1,2,4]
本题与上一题非常类似,需要dp用于记录遍历过的点,不同的点主要在于:
(1)像素填充只需要在上下左右四个方向遍历,而本题还需要考虑斜对角。
(2)像素填充给出了初始遍历的位置,而本题需要遍历每一个点,首先判断该点是否为水域点,若是则该点为中心继续遍历得到一块水域,最终记录水域数量。
def pondSizes(self, land):
m,n = len(land),len(land[0])
self.num = 0
output = []
dp = [[True for i in range(n)] for j in range(m)]
def dfs(i,j):
if i < 0 or j < 0 or i >=m or j >=n or dp[i][j] == False or land[i][j] != 0:
return
dp[i][j] = False
self.num = self.num + 1
dfs(i-1,j-1) #up left
dfs(i-1,j) #up
dfs(i-1,j+1) #up right
dfs(i+1,j-1) #down left
dfs(i+1,j) #down
dfs(i+1,j+1) #down right
dfs(i,j-1) #left
dfs(i,j+1) #right
for i in range(m):
for j in range(n):
if dp[i][j] and land[i][j] == 0:
dfs(i,j)
output.append(self.num)
self.num = 0
output.sort()
return output
给定一个二维的矩阵,包含 'X' 和 'O'(字母 O)。找到所有被 'X' 围绕的区域,并将这些区域里所有的 'O' 用 'X' 填充。
示例:
X X X X
X O O X
X X O X
X O X X
运行你的函数后,矩阵变为:X X X X
X X X X
X X X X
X O X X解释:
被围绕的区间不会存在于边界上,换句话说,任何边界上的 'O' 都不会被填充为 'X'。 任何不在边界上,或不与边界上的 'O' 相连的 'O' 最终都会被填充为 'X'。如果两个元素在水平或垂直方向相邻,则称它们是“相连”的。
本题与上两题也非常类似。初次看题,可能会采用前面的方法,遍历每一个点,判断是不是O,之后在以该点为中心进行扩散。但是实际上有一个更简便的方法,题目中说只要有O在边界上,那么与这个O水平垂直的其他O都不用被填充,也就是说我们只需要找到边界上的所有O和与其连接的O点,保留这些点就是了。
class Solution:
def solve(self, board):
if not board:
return
m,n = len(board),len(board[0])
dp = [[True for i in range(n)] for j in range(m)] #True表示等待遍历
def dfs(i,j):
if i < 0 or j < 0 or i >=m or j >=n or dp[i][j] == False or board[i][j] != 'o':
return
dp[i][j] = False
dfs(i-1,j) #up
dfs(i+1,j) #down
dfs(i,j-1) #left
dfs(i,j+1) #right
for i in range(m):
if 0 < i
你要开发一座金矿,地质勘测学家已经探明了这座金矿中的资源分布,并用大小为 m * n 的网格 grid 进行了标注。每个单元格中的整数就表示这一单元格中的黄金数量;如果该单元格是空的,那么就是 0。
为了使收益最大化,矿工需要按以下规则来开采黄金:
每当矿工进入一个单元,就会收集该单元格中的所有黄金。
矿工每次可以从当前位置向上下左右四个方向走。
每个单元格只能被开采(进入)一次。
不得开采(进入)黄金数目为 0 的单元格。
矿工可以从网格中 任意一个 有黄金的单元格出发或者是停止。
示例 1:输入:grid = [[0,6,0],[5,8,7],[0,9,0]]
输出:24
解释:
[[0,6,0],
[5,8,7],
[0,9,0]]
一种收集最多黄金的路线是:9 -> 8 -> 7。示例 2:输入:grid = [[1,0,7],[2,0,6],[3,4,5],[0,3,0],[9,0,20]]
输出:28
解释:
[[1,0,7],
[2,0,6],
[3,4,5],
[0,3,0],
[9,0,20]]
一种收集最多黄金的路线是:1 -> 2 -> 3 -> 4 -> 5 -> 6 -> 7。
算法的主要流程是:
依次遍历每一个点
if 这点的黄金为0,遍历下一个点
else 计算从这个点开始开采获取到的最大黄金量(通过 dfs函数)
计算目前为止的最大黄金量
返回黄金量
需要注意的点:
class Solution:
def getMaximumGold(self, grid):
m,n = len(grid), len(grid[0]) #高、宽
def dfs(a,b):
if a < 0 or b < 0 or a>= m or b >= n:
return 0
if dp[a][b] == False or grid[a][b] == 0:
return 0
dp[a][b] = False #已经遍历过改点了
up = dfs(a-1,b)
down = dfs(a+1,b)
left = dfs(a,b-1)
right = dfs(a,b+1)
dp[a][b] = True #!!!!!!!非常重要
return grid[a][b] + max(up,down,left,right)
maxvalue = 0
for i in range(m):
for j in range(n):
#从[i,j]点开采所能得到的黄金
if grid[i][j] == 0: #黄金数目为0,直接跳过不开采
continue
print(grid[i][j])
dp = [[True for k in range(n)] for g in range(m)] #记录从[i,j]开始开采允许遍历的到的点
maxvalue = max(maxvalue, dfs(i,j)) #目前为止的最大黄金
return maxvalue
给定一个二维网格和一个单词,找出该单词是否存在于网格中。
单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。
示例:board =
[
['A','B','C','E'],
['S','F','C','S'],
['A','D','E','E']
]给定 word = "ABCCED", 返回 true
给定 word = "SEE", 返回 true
给定 word = "ABCB", 返回 false
上一题需要计算从每个点开始开采获取到的最大黄金量,本题也可以类比于上一题,即列举出从每个点开始,长度为给定word的所有单词,判断给出的单词是否在我们找到的单词序列中。但是由于本题的特殊性可以设置两个约束条件:
[
['A','B','C','E'],
['S','F','C','S'],
['A','D','E','E']
],我们要找的单词是ASA,我们从board[0][0]:A开始遍历,第二个字母遍历到board[1][0]:S,第二个字母遍历到board[2][0]:A,此时直接就可以return True,也就是说,不用再遍历S的右位置board[1][1]:F。
class Solution:
def exist1(self, board, word):
#dp + Flag
#228 ms 15.2 MB
m,n = len(board),len(board[0])
length = len(word)
self.Flag = False
dp = [[True for k in range(n)] for g in range(m)]
def digui(s,index,a,b):
if a < 0 or b < 0 or a>= m or b >= n or dp[a][b] == False or self.Flag:
return
if board[a][b] != word[index]: #index表示word的第index个字母
return
s = s + board[a][b]
if index == length-1:
self.Flag = True
return self.Flag
index = index + 1
dp[a][b] = False
digui(s, index,a-1,b) #上
digui(s, index,a+1,b) #下
digui(s, index,a,b-1) #左
digui(s, index,a,b+1) #右
dp[a][b] = True #!!!!非常重要
for i in range(m):
for j in range(n):
digui('',0,i,j)
if self.Flag:
return self.Flag
return False
对于dp可以用visited代替。
def exist2(self, board, word):
#visited + Flag
#280 ms 15 MB
m,n = len(board),len(board[0])
length = len(word)
visited = set()
self.Flag = False
def digui(s,index,a,b):
if a < 0 or b < 0 or a>= m or b >= n or (a,b) in visited or self.Flag:
return
if board[a][b] != word[index]: #index表示word的第index个字母
return
s = s + board[a][b]
if index == length-1:
self.Flag = True
return self.Flag
index = index + 1
visited.add((a,b))
digui(s, index,a-1,b) #上
digui(s, index,a+1,b) #下
digui(s, index,a,b-1) #左
digui(s, index,a,b+1) #右
visited.remove((a,b)) #!!!!非常重要
for i in range(m):
for j in range(n):
digui('',0,i,j)
if self.Flag:
return self.Flag
return False