【leetcode】深度优先搜索

面试题 08.10. 颜色填充

面试题 16.19. 水域大小

130. 被围绕的区域

1219. 黄金矿工

79. 单词搜索

 

面试题 08.10. 颜色填充

编写函数,实现许多图片编辑软件都支持的「颜色填充」功能。

待填充的图像用二维数组 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

 面试题 16.19. 水域大小

你有一个用于表示一片土地的整数矩阵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

 130. 被围绕的区域

给定一个二维的矩阵,包含 '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-1:  #左右边界
                for j in [0,n-1]:
                    if dp[i][j] and board[i][j] == 'o':
                        dfs(i,j)
            else:
                for j in range(n):#上下边界
                    if dp[i][j] and board[i][j] == 'o':
                        dfs(i,j)
        for i in range(m):
            for j in range(n):
                if dp[i][j]:
                    board[i][j]='x'
        return board

 1219. 黄金矿工

你要开发一座金矿,地质勘测学家已经探明了这座金矿中的资源分布,并用大小为 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函数)
       计算目前为止的最大黄金量
返回黄金量

需要注意的点:

  •  处于每一个初始遍历点,都要申请一个dp用于记录从改点开采允许遍历到的点;
  • 在dfs函数中,当遍历到某一点时,首先将这一点的dp值设置为False,已经遍历过了,然后开始上下左右的遍历。但是最后一定要把dp值置回True,这一点是因为回溯。可以通过这个例子grids =[[1,0,7,0,0,0],[2,0,6,0,1,0],[3,5,6,7,4,2],[4,3,1,0,2,0],[3,0,5,0,20,0]],感受一下。
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

79. 单词搜索

给定一个二维网格和一个单词,找出该单词是否存在于网格中。

单词必须按照字母顺序,通过相邻的单元格内的字母构成,其中“相邻”单元格是那些水平相邻或垂直相邻的单元格。同一个单元格内的字母不允许被重复使用。

示例:board =
[
  ['A','B','C','E'],
  ['S','F','C','S'],
  ['A','D','E','E']
]

给定 word = "ABCCED", 返回 true
给定 word = "SEE", 返回 true
给定 word = "ABCB", 返回 false

 上一题需要计算从每个点开始开采获取到的最大黄金量,本题也可以类比于上一题,即列举出从每个点开始,长度为给定word的所有单词,判断给出的单词是否在我们找到的单词序列中。但是由于本题的特殊性可以设置两个约束条件:

  1. 遍历的第i个字母如果不等于给定word的第i个字母,就可以直接跳过;
  2. 当遍历到每一个字母都等于word中的字母时,可以直接跳出所有递归,返回True;这里我们用self.Flag来记录是否找到了单词。例如给定board =

[
  ['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

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值