算法刷题之九图

图是一种比较复杂的数据结构,不能说比较复杂,而是最复杂的。虽然接触到图的算法只有两道题,但是真的让我打开眼界,打开了深度优先和广度优先的大门。

  1. 岛屿数量
  2. 单词接龙

岛屿数量

给你一个由 '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

方法:找出图中连成一片的1,就是岛屿

  1. 我们需要建立一个 visited 数组用来记录某个位置是否被访问过。
  2. 对于一个为 1 且未被访问过的位置,我们递归进入其上下左右位置上为 1 的数,将其 visited 变成 true。
  3. 重复上述过程
  4. 找完相邻区域后,我们将结果 res 自增1,然后我们在继续找下一个为 1 且未被访问过的位置,直至遍历完.

DFS

DFS是按照某一个方向,一直找到不满足条件为止。
先是 向上 一直到边界,然后向下到边界,再向左,最后向右。按照某一个方向一直到底

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        if not grid: return 0
        
        count = 0
        for i in range(len(grid)):
            for j in range(len(grid[0])):
                if grid[i][j] == '1':
                    self.dfs(grid, i, j)
                    count += 1
                    
        return count
    # 深度优先算法。自己调用自己。
    # 只有当这一片的1全部都被置0之后,才会退出,否则会调用下去。
    def dfs(self, grid, i, j):
        if i < 0 or j < 0 or i >= len(grid) or j >= len(grid[0]) or grid[i][j] != '1':
            return 
        grid[i][j] = '0'
        self.dfs(grid, i + 1, j)
        self.dfs(grid, i - 1, j)
        self.dfs(grid, i, j + 1)
        self.dfs(grid, i, j - 1)

BFS

BFS 是搜索周边,一圈一圈扩大范围。以当前位置为中心,先判断上面是否符合条件,符合则加入队列中,在判断右边,加入队列,再oudo下面,最后左边。这样当前位置周围一圈都判断结束,同时队列中又保存有四个方向的数据,下一次循环四个方向分别再向外扩展。

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        count = 0
        for row in range(len(grid)):
            for col in range(len(grid[0])):
                if grid[row][col] == '1':  # 发现陆地
                    count += 1  # 结果加1
                    grid[row][col] = '0'  # 将其转为 ‘0’ 代表已经访问过
                    # 对发现的陆地进行扩张即执行 BFS,将与其相邻的陆地都标记为已访问
                    # 下面还是经典的 BFS 模板
                    land_positions = collections.deque()
                    land_positions.append([row, col])
                    while len(land_positions) > 0:
                        x, y = land_positions.popleft()
                        for new_x, new_y in [[x, y + 1], [x, y - 1], [x + 1, y], [x - 1, y]]:  # 进行四个方向的扩张
                            # 判断有效性
                            if 0 <= new_x < len(grid) and 0 <= new_y < len(grid[0]) and grid[new_x][new_y] == '1':
                                grid[new_x][new_y] = '0'  # 因为可由 BFS 访问到,代表同属一块岛,将其置 ‘0’ 代表已访问过
                                land_positions.append([new_x, new_y])
        return count

单词接龙

字典 wordList 中从单词 beginWord 和 endWord 的 转换序列 是一个按下述规格形成的序列:

序列中第一个单词是 beginWord 。
序列中最后一个单词是 endWord 。
每次转换只能改变一个字母。
转换过程中的中间单词必须是字典 wordList 中的单词。
给你两个单词 beginWord 和 endWord 和一个字典 wordList ,找到从 beginWord 到 endWord 的 最短转换序列 中的 单词数目 。如果不存在这样的转换序列,返回 0。

 
示例 1:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
输出:5
解释:一个最短转换序列是 "hit" -> "hot" -> "dot" -> "dog" -> "cog", 返回它的长度 5。
示例 2:

输入:beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
输出:0
解释:endWord "cog" 不在字典中,所以无法进行转换。

来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/word-ladder
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

``思路`
从起点词出发,每次变一个字母,经过 n 次变换,变成终点词,希望 n 尽量小。

我们需要找出邻接关系,比如hit变一个字母会变成_it、h_t、hi_形式的新词,再看该新词是否存在于单词表,如果存在,就找到了一个下一层的转换词。

同时,要避免重复访问,hot->dot->hot这样变回来是没有意义的,徒增转换的长度。

所以,确定了下一个转换词,将它从单词表中删除(单词表的单词是唯一的)。

下一层的单词可能有多个,都要考察,哪一条转换路径先遇到终点词,它就最短。

整理一下
把单词看作节点,由一个结点带出下一层的邻接点,用BFS去做。

维护一个队列,让起点词入列,level 为 1,然后出列考察。

将它的每个字符变成26字母之一,逐个看是否在单词表,如果在,该新词为下一层的转变词。

将它入列,它的 level +1,并从单词表中删去这个词。

出列、入列…重复,当出列的单词和终点词相同,说明遇到了终点词,返回它的 level。

当队列为空时,代表BFS结束,始终没有遇到终点词,没有路径通往终点,返回 0。

作者:xiao_ben_zhu
链接:https://leetcode-cn.com/problems/word-ladder/solution/shou-hua-tu-jie-127-dan-ci-jie-long-bfsde-dian-x-2/

class Solution:
    def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
        if not wordList and endWord not in wordList:
            return 0

        word_set = set(wordList)

        if beginWord in word_set:
            word_set.remove(beginWord)
        
        from collections import deque

        queue = deque()
        queue.append([beginWord, 0])
        while queue:
            word, level = queue.popleft()
            if word == endWord:
                return level + 1
            
            for i in range(len(word)):
                for j in range(26):
                    new_word = word[:i] + chr(ord('a') + j) + word[i+1:]
                    if new_word in word_set:
                        word_set.remove(new_word)
                        queue.append([new_word, level+1])
                    
        return 0

# 图的小结
图虽然只有两道题,但是把深度优先广度优先说的十分清楚。
深度优先: 循环 + 栈 
广度优先: 循环 + 队列

原文地址:https://www.cnblogs.com/goldsunshine/p/14930987.html