【leetcode】图论刷题tricks

深度优先搜索

深度优先搜索(dfs)是可一个方向去搜,直到遇到绝境了,搜不下去了,再换方向(换方向的过程就涉及到了回溯),所以用递归的方式来实现是最方便的

dfs实现三部曲

  • 确认递归函数,参数。一般情况,深搜需要二维数组数组结构保存所有路径,需要一维数组保存单一路径,这种保存结果的数组,我们可以定义一个全局变量,避免让递归函数参数过多
  • 确认中止条件。遇到终止条件不仅是结束本层递归,同时也是我们收获结果的时候
  • 处理目前搜索节点出发的路径。一般这里就是一个for循环的操作,去遍历 目前搜索节点 所能到的所有节点

LeetCode797题 所有可能的路径

class Solution:
    def allPathsSourceTarget(self, graph: List[List[int]]) -> List[List[int]]:
        self.res = []
        self.dfs(graph, 0, [0])
        return self.res
    
    def dfs(self, graph, node, path):
        # node代表当前路径最后一个元素
        if node == len(graph) - 1:
            self.res.append(path)
        for i in graph[node]:
            self.dfs(graph, i, path + [i])

 LeetCode200题 岛屿数量

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        res = 0
        self.grid = grid
        for i in range(len(self.grid)):
            for j in range(len(self.grid[0])):
                if self.grid[i][j] == '1':
                    res += 1
                    # dfs作用是将和i,j连在一起的陆地置0
                    self.dfs(i, j)
        return res
    
    def dfs(self, x, y):
        for i, j in [[0, -1], [0, 1], [1, 0], [-1, 0]]:
            if 0 <= x + i < len(self.grid) and 0 <= y + j < len(self.grid[0]) and self.grid[x + i][y + j] == '1':
                self.grid[x + i][y + j] = '0'
                self.dfs(x + i, y + j)

 LeetCode695题 岛屿的最大面积

class Solution:
    def maxAreaOfIsland(self, grid: List[List[int]]) -> int:
        res = 0
        self.grid = grid
        for i in range(len(self.grid)):
            for j in range(len(self.grid[0])):
                if self.grid[i][j] == 1:
                    self.count = 0
                    self.dfs(i, j)
                    res = max(res, self.count)
        return res

    
    def dfs(self, x, y):
        self.count += 1
        self.grid[x][y] = 0
        for i, j in [[0, 1], [0, -1], [1, 0], [-1, 0]]:
            if 0 <= x + i < len(self.grid) and 0 <= y + j < len(self.grid[0]) and self.grid[x + i][y + j] == 1:
                self.grid[x + i][y + j] = 0
                self.dfs(x + i, y + j)

LeetCode417题 太平洋大西洋水流问题

从太平洋边上的节点 逆流而上,将遍历过的节点都标记上。 从大西洋的边上节点 逆流而长,将遍历过的节点也标记上。 然后两方都标记过的节点就是既可以流太平洋也可以流大西洋的节点

class Solution:
    def pacificAtlantic(self, heights: List[List[int]]) -> List[List[int]]:
        res = []
        m = len(heights)
        n = len(heights[0])
        self.heights = heights
        self.is_pac = [[False] * n for _ in range(m)]
        self.is_atl = [[False] * n for _ in range(m)]
        # 以边界元素为种子,分别进行深度优先搜索
        for i in range(n):
            self.is_pac[0][i] = True
            self.dfs(0, i, self.is_pac)
            self.is_atl[m - 1][i] = True
            self.dfs(m - 1, i, self.is_atl)
        for i in range(m):
            self.is_pac[i][0] = True
            self.dfs(i, 0, self.is_pac)
            self.is_atl[i][n - 1] = True
            self.dfs(i, n - 1, self.is_atl)

        for i in range(m):
            for j in range(n):
                if self.is_pac[i][j] and self.is_atl[i][j]:
                    res.append([i, j])
        return res

    def dfs(self, x, y, path):
        for i, j in [[0, 1], [1, 0], [-1, 0], [0, -1]]:
            if 0 <= x + i < len(self.heights) and 0 <= y + j < len(self.heights[0]):
                if self.heights[x + i][y + j] >= self.heights[x][y] and not path[x + i][y + j]:
                    path[x + i][y + j] = True
                    self.dfs(x + i, y + j, path)

LeetCode827题 最大人工岛

第一步:一次遍历地图,得出各个岛屿的面积,并做编号记录

第二步:在遍历地图,遍历0的方格(因为要将0变成1),并统计该1(由0变成的1)周边岛屿面积,将其相邻面积相加在一起,遍历所有 0 之后,就可以得出 选一个0变成1 之后的最大面积

class Solution:
    def largestIsland(self, grid: List[List[int]]) -> int:
        res = 0
        m = len(grid)
        n = len(grid[0])
        self.grid = grid
        self.visited = [[False] * n for _ in range(m)]
        zero_index = []
        # 记录连通图数量
        connected_graph_num = 0
        for i in range(m):
            for j in range(n):
                if not self.visited[i][j] and grid[i][j] == 1:
                    # 连通图的坐标集合
                    tmp_res = [[i, j]]
                    self.dfs(i, j, tmp_res)
                    res = max(res, len(tmp_res))
                    # 将连通图的id、连通图面积赋值在grid每个岛屿元素上
                    for x, y in tmp_res:
                        grid[x][y] = [connected_graph_num, len(tmp_res)]
                    connected_graph_num += 1
                elif grid[i][j] == 0:
                    zero_index.append([i, j])
        
        for x, y in zero_index:
            # 记录已经遍历过的连通图id
            connected_graph_id = []
            tmp_s = 1
            for i, j in [[0, 1], [1, 0], [0, -1], [-1, 0]]:
                if 0 <= x + i < len(self.grid) and 0 <= y + j < len(self.grid[0]) and grid[x + i][y + j] != 0:
                    index, s = grid[x + i][y + j]
                    if index not in connected_graph_id:
                        tmp_s += s
                        connected_graph_id.append(index)
            res = max(res, tmp_s)
        return res


    def dfs(self, x, y, res):
        self.visited[x][y] = True
        for i, j in [[0, 1], [1, 0], [0, -1], [-1, 0]]:
            if 0 <= x + i < len(self.grid) and 0 <= y + j < len(self.grid[0]):
                if not self.visited[x + i][y + j] and self.grid[x + i][y + j] == 1:
                    res.append([x + i, y + j])
                    self.dfs(x + i, y + j, res)

广度优先搜索

广度优先搜索(bfs)是先把本节点所连接的所有节点遍历一遍,走到下一个节点的时候,再把连接节点的所有节点遍历一遍,搜索方向更像是四面八方的搜索过程

广搜的搜索方式就适合于解决两个点之间的最短路径问题。因为广搜是从起点出发,以起始点为中心一圈一圈进行搜索,一旦遇到终点,记录之前走过的节点就是一条最短路

bfs代码模板

保存遍历元素的容器,用队列、用栈都是可以的,但习惯用队列(可以保证每层转圈方向统一)

from collections import deque

dir = [(0, 1), (1, 0), (-1, 0), (0, -1)]  # 创建方向元素


def bfs(grid, visited, x, y):
    queue = deque()  # 初始化队列
    queue.append((x, y))  # 放入第一个元素/起点
    visited[x][y] = True  # 标记为访问过的节点
    while queue:  # 遍历队列里的元素
        curx, cury = queue.popleft()  # 取出第一个元素
        for dx, dy in dir:  # 遍历四个方向
            nextx, nexty = curx + dx, cury + dy
            if nextx < 0 or nextx >= len(grid) or nexty < 0 or nexty >= len(grid[0]):  # 越界了,直接跳过
                continue
            if not visited[nextx][nexty]:  # 如果节点没被访问过  
                queue.append((nextx, nexty))  # 加入队列
                visited[nextx][nexty] = True  # 标记为访问过的节点

LeetCode200题 岛屿数量

class Solution:
    def numIslands(self, grid: List[List[str]]) -> int:
        res = 0
        self.grid = grid
        for i in range(len(self.grid)):
            for j in range(len(self.grid[0])):
                if self.grid[i][j] == '1':
                    res += 1
                    self.bfs(i, j)
        return res


    def bfs(self, x, y):
        from collections import deque
        queue = deque()
        queue.append([x, y])
        while queue:
            x, y = queue.popleft()
            for i, j in [[0, -1], [0, 1], [1, 0], [-1, 0]]:
                if 0 <= x + i < len(self.grid) and 0 <= y + j < len(self.grid[0]) and self.grid[x + i][y + j] == '1':
                    self.grid[x + i][y + j] = '0'
                    queue.append([x + i, y + j])

LeetCode127题 单词接龙

求起点和终点的最短路径长度,这里无向图求最短路,广搜最为合适,广搜只要搜到了终点,那么一定是最短的路径。因为广搜就是以起点中心向四周扩散的搜索

class Solution:
    def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
        wordSet = set(wordList)
        if len(wordSet)== 0 or endWord not in wordSet:
            return 0
        # 记录到每个单词的最小路径
        mapping = {beginWord: 1}
        queue = deque([beginWord])
        while queue:
            word = queue.popleft()
            path = mapping[word]
            for i in range(len(word)):
                word_list = list(word)
                # 以26个字符作为广搜的对象
                for j in range(26):
                    word_list[i] = chr(ord('a') + j)
                    newWord = ''.join(word_list)
                    if newWord == endWord:
                        return path + 1
                    if newWord in wordSet and newWord not in mapping:
                        mapping[newWord] = path + 1
                        queue.append(newWord)                      
        return 0

 LeetCode841题 钥匙和房间

class Solution:
    def canVisitAllRooms(self, rooms: List[List[int]]) -> bool:
        visited = set()
        from collections import deque
        queue = deque([0])
        while queue:
            index = queue.popleft()
            visited.add(index)
            if len(visited) == len(rooms):
                return True
            for i in rooms[index]:
                if i not in visited:
                    queue.append(i)
        return False

并查集

并查集(Union-find Sets)常用来解决连通性问题。当我们需要判断两个元素是否在同一个集合里的时候,就要想到用并查集。并查集主要有两个功能:

  • 将两个元素添加到一个集合中
  • 判断两个元素在不在同一个集合

并查集的基本操作:

  • 初始化
  • 合并(union,将两个节点连在同一个根节点上)
  • 查询(find,判断这个节点的祖先节点是哪个)

所以判断两个节点是否在同一个集合(是否连通),就是判断两个节点是不是同一个根节点

路径压缩后的并查集时间复杂度在O(logn)与O(1)之间,且随着查询或者合并操作的增加,时间复杂度会越来越趋于O(1),在第一次查询的时候,相当于是n叉树上从叶子节点到根节点的查询过程,时间复杂度是logn,但路径压缩后,后面的查询操作都是O(1)

LeetCode1971题 寻找图中是否存在路径

class Solution:
    def validPath(self, n: int, edges: List[List[int]], source: int, destination: int) -> bool:
        # 初始化
        father = [i for i in range(n)]
        # find
        def find(i):
            if father[i] == i:
                return i
            # 路径压缩操作,否则会超时
            father[i] = find(father[i])
            return father[i]
        # union
        for u, v in edges:
            father[find(u)] = find(v)
        return find(source) == find(destination)

LeetCode684题 冗余连接

可以从前向后union每一条边(因为优先让前面的边连上),边的两个节点如果不在同一个集合,就加入集合;如果边的两个节点已经出现在同一个集合里,说明边的两个节点已经连在一起了,再加入这条边一定就出现环了。 

class Solution:
    def findRedundantConnection(self, edges: List[List[int]]) -> List[int]:
        res = []
        father = [i for i in range(len(edges))]
        def find(i):
            if i == father[i]:
                return i
            father[i] = find(father[i])
            return father[i]
        for i, j in edges:
            a = find(i - 1)
            b = find(j - 1)
            if a == b:
                res = [i, j]
            father[a] = b
        return res

 LeetCode685题 冗余连接II

class Solution:
    def findRedundantDirectedConnection(self, edges: List[List[int]]) -> List[int]:
        res = []
        father = []
        father_visited = dict()
        r = []
        for i in range(len(edges)):
            father.append(i)
            if edges[i][1] in father_visited:
                father_visited[edges[i][1]].append(edges[i])
                r = father_visited[edges[i][1]]
            else:
                father_visited[edges[i][1]] = [edges[i]]
        def find(i):
            if i == father[i]:
                return i
            father[i] = find(father[i])
            return father[i]
        for i, j in edges:
            if len(r) == 0 or j != r[0][1]:
                a = find(i - 1)
                b = find(j - 1)
                if a == b:
                    res = [i, j]
                father[b] = a
        if len(r) > 0:
            for i, j in r:
                a = find(i - 1)
                b = find(j - 1)
                if a == b:
                    res = [i, j]
                father[b] = a
        return res

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值