Leetcode - 图(一)

目录

997. 找到小镇的法官

1042. 不邻接植花

323. 无向图中连通分量的数目🔒

261. 以图判树🔒

785. 判断二分图

1091. 二进制矩阵中的最短路径

752. 打开转盘锁

773. 滑动谜题

980. 不同路径 III

1135. 最低成本联通所有城市


997. 找到小镇的法官

https://leetcode-cn.com/problems/find-the-town-judge/

在一个小镇里,按从 1 到 N 标记了 N 个人。传言称,这些人中有一个是小镇上的秘密法官。如果小镇的法官真的存在,那么:

  • 小镇的法官不相信任何人。
  • 每个人(除了小镇法官外)都信任小镇的法官。
  • 只有一个人同时满足属性 1 和属性 2 。

给定数组 trust,该数组由信任对 trust[i] = [a, b] 组成,表示标记为 a 的人信任标记为 b 的人。如果小镇存在秘密法官并且可以确定他的身份,请返回该法官的标记。否则,返回 -1。

提示:

  • 1 <= N <= 1000
  • trust.length <= 10000
  • trust[i] 是完全不同的
  • trust[i][0] != trust[i][1]
  • 1 <= trust[i][0], trust[i][1] <= N

题解

一:这是一道有向图问题。 并且法官实际上就是出度为0,入度为 N - 1的节点。因此一个思路就是统计所有人的入度和出度信息,将满足出度为0,入度为 N - 1的节点输出。这里用两个数组 in_degree 和 out_degree 分别记录入度和出度的信息,为了简单起见,我们初始化的数组长度为 N + 1,而不是 N。

class Solution(object):
    def findJudge(self, N, trust):
        """
        :type N: int
        :type trust: List[List[int]]
        :rtype: int
        """
        # out_degree信任别人,in_degree被别人信任
        out_degree = [0 for _ in range(N + 1)]
        in_degree = [0 for _ in range(N + 1)]

        for pair in trust:
            # pair[0] 信任别人
            out_degree[pair[0]] += 1
            # pair[1]被信任
            in_degree[pair[1]] += 1
        
        for  i in range(1, N + 1):
            if out_degree[i] == 0 and in_degree[i] == N - 1:
                return i 
        return -1

1042. 不邻接植花

https://leetcode-cn.com/problems/flower-planting-with-no-adjacent/

有 N 个花园,按从 1 到 N 标记。在每个花园中,你打算种下四种花之一。paths[i] = [x, y] 描述了花园 x 到花园 y 的双向路径。另外,没有花园有 3 条以上的路径可以进入或者离开。你需要为每个花园选择一种花,使得通过路径相连的任何两个花园中的花的种类互不相同。以数组形式返回选择的方案作为答案 answer,其中 answer[i] 为在第 (i+1) 个花园中种植的花的种类。花的种类用  1, 2, 3, 4 表示。保证存在答案。

提示:

  • 1 <= N <= 10000
  • 0 <= paths.size <= 20000
  • 不存在花园有 4 条或者更多路径可以进入或离开。
  • 保证存在答案。

题解

一:用邻接表数组构造无向图,假设初始时每个花园都是0号花,然后按1到N(0到N-1)的顺序给每个花园种花。对每个花园,都有1、2、3、4四种花可选,用used_color数组标记每种花是否被与当前花园相通的邻接花园使用过,标记完后,从剩余的没被使用过的花中选取号数最小的花种在当前花园。直至对所有花园完成种植。

class Solution(object):
    def gardenNoAdj(self, N, paths):
        """
        :type N: int
        :type paths: List[List[int]]
        :rtype: List[int]
        """     
        graph = [[] for _ in range(N)]
        for path in paths:
            graph[path[0] - 1].append(path[1] - 1)
            graph[path[1] - 1].append(path[0] - 1)  
        
        res = [0 for _ in range(N)]

        for i in range(N):
            used_color = [False] * 5
            for item in graph[i]:
                if item < i:
                    used_color[res[item]] = True
            for j in range(1, 5):
                if not used_color[j]:
                    res[i] = j
                    break
        return res

323. 无向图中连通分量的数目🔒

https://leetcode-cn.com/problems/number-of-connected-components-in-an-undirected-graph/

给定编号从 0 到 n-1 的 n 个节点和一个无向边列表(每条边都是一对节点),请编写一个函数来计算无向图中连通分量的数目。

注意:你可以假设在 edges 中不会出现重复的边。而且由于所以的边都是无向边,[0, 1] 与 [1, 0]  相同,所以它们不会同时在 edges 中出现。

题解

一:BFS,广度优先搜索

from collections import deque
class Solution(object):
    def countComponents(self, n, edges):
        """
        :type n: int
        :type edges: List[List[int]]
        :rtype: int
        """
        def bfs(v):
            q = deque()
            q.append(v)
            visited[v] = True 

            while q:
                vertix = q.popleft() 
                for V in g[vertix]:
                    if not visited[V]:
                        q.append(V)
                        visited[V] = True

        g = [[] for _ in range(n)]
        for edge in edges:
            g[edge[0]].append(edge[1])
            g[edge[1]].append(edge[0])

        visited, res = [False] * n, 0

        for i in range(n):
            if not visited[i]:
                bfs(i)
                res += 1 
        return res

二:dfs

class Solution(object):
    def countComponents(self, n, edges):
        def dfs(vertix):
            for item in g[vertix]:
                if not visited[item]:
                    visited[item] = True 
                    dfs(item)
            

        g = [[] for _ in range(n)]
        for edge in edges:
            g[edge[0]].append(edge[1])
            g[edge[1]].append(edge[0])

        visited, res = [False] * n, 0

        for i in range(n):
            if not visited[i]:
                dfs(i)
                res += 1 
        return res 

261. 以图判树🔒

https://leetcode-cn.com/problems/graph-valid-tree/

给定从 0 到 n-1 标号的 n 个结点,和一个无向边列表(每条边以结点对来表示),请编写一个函数用来判断这些边是否能够形成一个合法有效的树结构。

示例 1:输入: n = 5, 边列表 edges = [[0,1], [0,2], [0,3], [1,4]],输出: true
示例 2:输入: n = 5, 边列表 edges = [[0,1], [1,2], [2,3], [1,3], [1,4]],输出: false
注意:你可以假定边列表 edges 中不会出现重复的边。由于所有的边是无向边,边 [0,1] 和边 [1,0] 是相同的,因此不会同时出现在边列表 edges 中。

题解

一:判断一张图是否是一棵树,必须保证图是连通的(即只有一个连通分量),且没有环。

class Solution(object):

    def validTree(self, n, edges):
        """
        :type n: int
        :type edges: List[List[int]]
        :rtype: bool
        """
        def dfs(v, parent):
            visited[v] = True 
            for w in G[v]:
                if not visited[w]:
                    if dfs(w, v):
                        return True
                else:
                    if w != parent:
                        return True
            return False

        visited = [False] * n 
        G = [[] for _ in range(n)]
        cccount = 0

        for edge in edges:
            G[edge[0]].append(edge[1])
            G[edge[1]].append(edge[0])

        for v in range(n):
            if not visited[v]:
                if cccount >= 1 or dfs(v, v):
                    return False
                cccount += 1
        return True

二:BFS

from collections import deque
class Solution(object):
    def validTree(self, n, edges):
        """
        :type n: int
        :type edges: List[List[int]]
        :rtype: bool
        """
        def bfs(s):
            queue = deque()
            queue.append(s)
            visited[s] = True
            pre[s] = s

            while queue:
                v = queue.popleft()
                for w in G[v]:
                    if not visited[w]:
                        queue.append(w)
                        visited[w] = True
                        pre[w] = v 
                    elif w != pre[v]:
                        return True
            return False

        G = [[] for _ in range(n)]
        for edge in edges:
            G[edge[0]].append(edge[1])
            G[edge[1]].append(edge[0])

        visited = [False] * n 
        pre = [-1] * n
        cccount = 0
        
        for v in range(n):
            if not visited[v]:
                if cccount >= 1 or bfs(v):
                    return False
                cccount += 1

        return True

 

785. 判断二分图

https://leetcode-cn.com/problems/is-graph-bipartite/

题解

一:这边需要注意的是,graph是以邻接表给出的无向图。与之前的不同的是,前面的题目给出的是顶点个数,和边的信息,需要我们自己去把它转换成邻接表的形式。而这边graph已经给我们做好了,顶点数就是graph的长度。判断二分图用染色的概念来做。

class Solution(object):
    def isBipartite(self, graph):
        """
        :type graph: List[List[int]]
        :rtype: bool
        """
        def dfs(v, color):
            visited[v] = True 
            colors[v] = color
            for w in graph[v]:
                if not visited[w]:
                    if not dfs(w, 1 - color):
                        return False 
                else:
                    if colors[w] == colors[v]:
                        return False 
            return True
        
        V = len(graph)
        visited = [False] * V
        colors = [-1] * V

        for v in range(V):
            if not visited[v]:
                if not dfs(v, 0):
                    return False

        return True

二:BFS

from collections import deque
class Solution(object):
    def isBipartite(self, graph):
        def bfs(s, color):
            queue = deque()
            queue.append(s)
            visited[s] = True 
            colors[s] = color 

            while queue:
                v = queue.popleft()
                for w in graph[v]:
                    if not visited[w]:
                        queue.append(w)
                        visited[w] = True
                        colors[w] = 1 - colors[v] 
                    elif colors[w] == colors[v]:
                        return False 
            return True
        
        n = len(graph)
        visited = [False] * n
        colors = [-1] * n 

        for v in range(n):
            if not visited[v]:
                if not bfs(v, 0):
                    return False 
        return True

1091. 二进制矩阵中的最短路径

https://leetcode-cn.com/problems/shortest-path-in-binary-matrix/

提示:1 <= grid.length == grid[0].length <= 100;grid[i][j] 为 0 或 1

题解

一:BFS,找到的就是最短路径,由于可以往8个方向走,会出现从一个点走出再回到该点的现象,故需要visited数组来标识已访问过的格子,为了便于得到路径长度,在BFS的时候添加距离数组dis。

from collections import deque
class Solution(object):
    def shortestPathBinaryMatrix(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        def check(x, y):
            return 0 <= x < len(grid) and 0 <= y < len(grid[0]) 
        
        n = len(grid)
        if grid[0][0] == 1 or grid[n - 1][n - 1] == 1:
            return -1 

        queue= deque() 
        dirs = [[0, 1], [1, 1], [1, 0], [1, -1], [0, -1], [-1, -1], [0, -1], [-1, 1]]
        dis = [[-1] * n for _ in range(n)]
        visited = [[False] * n for _ in range(n)]

        queue.append(0)
        visited[0][0] = True
        dis[0][0] = 1 

        while queue:
            cur = queue.popleft()
            x, y = cur // n, cur % n
            for d in dirs:
                i, j = x + d[0], y + d[1]
                if check(i, j) and grid[x][y] == 0 and not visited[i][j]:
                    queue.append(i * n + j)
                    visited[i][j] = True
                    dis[i][j] = dis[x][y] + 1
                    if i == n - 1 and j == n - 1:
                        return dis[n - 1][n - 1]   
        return dis[n - 1][n - 1]     

752. 打开转盘锁

https://leetcode-cn.com/problems/open-the-lock/

你有一个带有四个圆形拨轮的转盘锁。每个拨轮都有10个数字: '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' 。每个拨轮可以自由旋转:例如把 '9' 变为  '0','0' 变为 '9' 。每次旋转都只能旋转一个拨轮的一位数字。锁的初始数字为 '0000' ,一个代表四个拨轮的数字的字符串。列表 deadends 包含了一组死亡数字,一旦拨轮的数字和列表里的任何一个元素相同,这个锁将会被永久锁定,无法再被旋转。字符串 target 代表可以解锁的数字,你需要给出最小的旋转次数,如果无论如何不能解锁,返回 -1。

提示:死亡列表 deadends 的长度范围为 [1, 500]。目标数字 target 不会在 deadends 之中。每个 deadends 和 target 中的字符串的数字会在 10,000 个可能的情况 '0000' 到 '9999' 中产生。

题解

一:这边的状态(即四个数字的一个组合)可以看作图的顶点,拨轮的方式就是顶点的转移(即顶点的边)。

求:最少的步数。类似图论中单源最短路径,可以用BFS求解。

小技巧

  • 关于取余,因为可以每次拨动都有两种情况+1和-1,其中+1可以用+1对10求余,-1也可用-1对10求余,但是当原先为0时,-1后变成负数,负数对10取余,不同的语言处理不同,这边可以将-1对10取余,变成+9对10求余。
  • 每次查询状态是否在死亡列表中耗时太长,改用集合来做。
from collections import deque


class Solution(object):
    def openLock(self, deadends, target):
        """
        :type deadends: List[str]
        :type target: str
        :rtype: int
        """
        if '0000' in deadends:
            return -1
        if target == '0000':
            return 0
        deadsets = set(deadends)
        queue = deque()
        visited = [False] * 10000
        step = [-1] * 10000
        queue.append('0000')
        visited[0] = True
        step[0] = 0

        while queue:
            cur = queue.popleft()
            cnt = step[int(cur)] + 1
            for i in range(4):
                for delta in [1, 9]:
                    num = (int(cur[i]) + delta) % 10
                    next1 = cur[:i] + str(num) + cur[i + 1:]
                    next1_num = int(next1)
                    if next1 == target:
                        return cnt
                    if next1 not in deadsets and not visited[next1_num]:
                        queue.append(next1)
                        step[next1_num] = step[int(cur)] + 1
                        visited[next1_num] = True
        return -1

773. 滑动谜题

https://leetcode-cn.com/problems/sliding-puzzle/

提示:board 是一个如上所述的 2 x 3 的数组。board[i][j] 是一个 [0, 1, 2, 3, 4, 5] 的排列。

题解

一:还是状态的问题,类似上一题,我们将6个数字的组合看成图的顶点,用字符串来代表这6个数字的组合,状态的移动,其实就是对应位置字符的交换。时间复杂度O(n!),n是矩阵中元素的个数。

from collections import deque
class Solution(object):
    def slidingPuzzle(self, board):
        """
        :type board: List[List[int]]
        :rtype: int
        """
        def check(x, y):
            return 0 <= x < 2 and 0 <= y < 3

        queue = deque()
        visited = {}
        cur = ''.join(str(i) for i in board[0]) + ''.join(str(i) for i in board[1])
        if cur == "123450":
            return 0

        queue.append(cur)
        visited[cur] = 0 
        dirs = [[0, 1], [1, 0], [0, -1], [-1, 0]]

        while queue:
            cur = queue.popleft()
            zreo = cur.find('0')
            zx, zy = zreo // 3, zreo % 3
            nexts = []
            for d in dirs:
                r, c = zx + d[0], zy + d[1]
                if check(r, c):
                    i = r * 3 + c
                    start, end = min(zreo, i), max(zreo, i)
                    state = cur[:start] + cur[end] + cur[start + 1 : end] + cur[start] + cur[end+1:] 
                    nexts.append(state)
            
            for state in nexts:
                if state not in visited:
                    queue.append(state)
                    visited[state] = visited[cur] + 1
                    if state == "123450":
                        return visited[state]

        return -1

980. 不同路径 III

https://leetcode-cn.com/problems/unique-paths-iii/

题解

一:典型的哈密尔顿路径问题。注意:这边 dfs(start[0], start[1], total + 1)传入的是total+1,因为在程序运行时,起始点也算进去了,会进行left-=1操作,所以传入的时候补上

class Solution(object):
    def uniquePathsIII(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        def valid(x, y):
            return 0 <= x < len(grid) and 0 <= y < len(grid[0])

        def dfs(x, y, left):
            visited[x][y] = True 
            left -= 1 
            res = 0

            for d in dirs:
                dx, dy = x + d[0], y + d[1]
                if valid(dx, dy) and not visited[dx][dy]:
                    if grid[dx][dy] == 0:
                        res += dfs(dx, dy, left)
                    elif grid[dx][dy] == 2 and left == 0:
                        res += 1

            visited[x][y] = False
            return res


        m, n = len(grid), len(grid[0])
        total, start = 0, (-1, -1)

        for i in range(m):
            for j in range(n):
                if grid[i][j] == 0:
                    total += 1
                elif grid[i][j] == 1:
                    start = (i, j)
                
        dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]]
        visited = [[False] * n for _ in range(m)]
        
        return dfs(start[0], start[1], total + 1)
        
class Solution(object):
    def uniquePathsIII(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        def valid(x, y):
            return 0 <= x < len(grid) and 0 <= y < len(grid[0])

        def dfs(x, y, left):
            if left == 0 and grid[x][y] == 2:
                return 1

            visited[x][y] = True 
            left -= 1 
            res = 0
            
            for d in dirs:
                dx, dy = x + d[0], y + d[1]
                if valid(dx, dy) and not visited[dx][dy]:
                    if grid[dx][dy] == 0 or grid[dx][dy] == 2:
                        res += dfs(dx, dy, left)

            visited[x][y] = False
            return res

        m, n = len(grid), len(grid[0])
        total, start = 0, (-1, -1)

        for i in range(m):
            for j in range(n):
                if grid[i][j] == 0:
                    total += 1
                elif grid[i][j] == 1:
                    start = (i, j)
                
        dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]]
        visited = [[False] * n for _ in range(m)]
        
        return dfs(start[0], start[1], total + 1)
        

法二:状态压缩,将visited数组用二进制整数表示

class Solution(object):
    def uniquePathsIII(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        def valid(x, y):
            return 0 <= x < len(grid) and 0 <= y < len(grid[0])

        def dfs(visited, start, left, n):
            x, y = start // n, start % n
            if left == 0 and grid[x][y] == 2:
                return 1

            visited += (1 << start)
            left -= 1 
            res = 0
            
            for d in dirs:
                dx, dy = x + d[0], y + d[1]
                nexts = dx * n + dy
                if valid(dx, dy) and (visited & (1 << nexts)) == 0:
                    if grid[dx][dy] == 0 or grid[dx][dy] == 2:
                        res += dfs(visited, nexts, left, n)
            return res

        m, n = len(grid), len(grid[0])
        total, start = 0, -1

        for i in range(m):
            for j in range(n):
                if grid[i][j] == 0:
                    total += 1
                elif grid[i][j] == 1:
                    start = i * n + j
                    
                
        dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]]
        visited = 0
        
        return dfs(visited, start, total + 1, n)

法三:记忆化搜索,超出时间限制。记忆化搜索时间复杂度O(2*2^n),回溯的时间复杂度O(n!)。但在实际工程中不好说哪个更快,因为回溯也是有剪枝的,可能并不需要把n个顶点的所有排列组合都访问一遍,因为有些顶点不相邻,则这种排列组合不存在,所以通常达不到n!的时间复杂度。很多时候看数据,若数据存在大量的重复搜索的情况,记忆化搜索更好,否则也可能回溯更好。记忆化搜索的一大缺点就是占内存,有一个1<<G.V(),在这么大的内存中赋值也是要大量时间的。

class Solution(object):
    def uniquePathsIII(self, grid):
        """
        :type grid: List[List[int]]
        :rtype: int
        """
        def valid(x, y):
            return 0 <= x < len(grid) and 0 <= y < len(grid[0])

        def dfs(visited, start, left, n):
            if memo[visited][start] != -1:
                return memo[visited][start]
            
            x, y = start // n, start % n
            if left == 0 and grid[x][y] == 2:
                memo[visited][start] = 1
                return 1

            visited += (1 << start)
            left -= 1 
            res = 0
            
            for d in dirs:
                dx, dy = x + d[0], y + d[1]
                nexts = dx * n + dy
                if valid(dx, dy) and (visited & (1 << nexts)) == 0:
                    if grid[dx][dy] == 0 or grid[dx][dy] == 2:
                        res += dfs(visited, nexts, left, n)
            memo[visited][start] = res
            return res

        m, n = len(grid), len(grid[0])
        total, start = 0, -1

        for i in range(m):
            for j in range(n):
                if grid[i][j] == 0:
                    total += 1
                elif grid[i][j] == 1:
                    start = i * n + j
                    
                
        dirs = [[-1, 0], [1, 0], [0, -1], [0, 1]]
        visited = 0
        memo = [[-1] * (m * n) for _ in range(1<< (m * n))]
        
        return dfs(visited, start, total + 1, n)
        

1135. 最低成本联通所有城市

https://leetcode-cn.com/problems/connecting-cities-with-minimum-cost/

提示:1 <= N <= 10000,1 <= conections.length <= 10000,1 <= conections[i][0], conections[i][1] <= N,0 <= conections[i][2] <= 10^5,conections[i][0] != conections[i][1]

题解

一:这是一道典型的最小生成树问题,Kruskal算法+并查集。

class Solution(object):
    def minimumCost(self, N, connections):
        """
        :type N: int
        :type connections: List[List[int]]
        :rtype: int
        """
        def dfs(v):
            visited[v] = True 
            for w in G[v]:
                if not visited[w]:
                    dfs(w)
        
        def find(p):
            if p != parent[p]:
                parent[p] = find(parent[p])
            return parent[p]

        G = [[] for _ in range(N)]
        for edge in connections:
            v, w, _ = edge 
            G[v - 1].append(w - 1)
            G[w - 1].append(v - 1)
        
        # dfs判断是否连通,不连通则不存在最小生成树
        visited = [False] * N
        cc = 0
        for v in range(N):
            if not visited[v]:
                if cc >= 1:
                    return -1
                dfs(v)
                cc += 1

        parent = [i for i in range(N + 1)]
        edges = sorted(connections, key=lambda edge: edge[2])

        res = 0
        for edge in edges:
            v, w, weight = edge 
            v_root, w_root = find(v), find(w)
            if v_root != w_root:
                res += weight 
                parent[v_root] = w_root
        return res

二:Prim算法,提交一直未通过,查了好久,才想到有可能会存在平行边,面对平行边我们应该保留成本最小的,即如下代码,而不是直接赋值,直接赋值会保留最后一条边,而不是成本最小的一条边。法一,没有影响是因为,我们直接用connections的权重直接排序的,不会产生覆盖掉的情况,所有的边都保留了下来,包括平行边,但我们每次都是考虑最小的,故不会有影响。

G[v][w] = weight if w not in G[v] or G[v][w] > weight else G[v][w]
G[w][v] = weight if v not in G[w] or G[w][v] > weight else G[w][v]
from heapq import heappop, heappush
class Solution(object):
    def minimumCost(self, N, connections):
        def dfs(v):
            visited[v] = True 
            for w in G[v]:
                if not visited[w]:
                    dfs(w)

        G = [{} for _ in range(N + 1)]
        for edge in connections:
            v, w, weight = edge 
            G[v][w] = weight
            G[w][v] = weight
        
        # dfs判断是否连通,不连通则不存在最小生成树
        visited = [False] * (N + 1)
        cc = 0
        for v in range(1, N + 1) :
            if not visited[v]:
                if cc >= 1:
                    return -1
                dfs(v)
                cc += 1

        # prim
        pq = [] 
        visited = [False] * (N + 1)
        visited[1] = True
        for w in G[1]:
            heappush(pq, [G[1][w], 1, w])

        res = 0
        while pq:
            weight, v, w = heappop(pq)
            if visited[v] and visited[w]:
                continue 
            res += weight

            new_v = v if visited[w] else w 
            visited[new_v] = True

            for w in G[new_v]:
                if not visited[w]:
                    heappush(pq, [G[new_v][w], new_v, w])
        return res

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值