LeetCode 207. Course Schedule (课程调度)

原题

There are a total of n courses you have to take, labeled from 0 to n-1.

Some courses may have prerequisites, for example to take course 0 you have to first take course 1, which is expressed as a pair: [0,1]

Given the total number of courses and a list of prerequisite pairs, is it possible for you to finish all courses?

Example 1:

Input: 2, [[1,0]] 
Output: true
Explanation: There are a total of 2 courses to take. 
             To take course 1 you should have finished course 0. So it is possible.

Example 2:

Input: 2, [[1,0],[0,1]]
Output: false
Explanation: There are a total of 2 courses to take. 
             To take course 1 you should have finished course 0, and to take course 0 you should
             also have finished course 1. So it is impossible.

Note:

  1. The input prerequisites is a graph represented by a list of edges, not adjacency matrices. Read more about how a graph is represented.
  2. You may assume that there are no duplicate edges in the input prerequisites.

Reference Answer

思路分析

同样是拓扑排序,但是换了个做法,使用DFS。这个方法是,我们每次找到一个新的点,判断从这个点出发是否有环。

具体做法是使用一个visited数组,当visited[i]值为0,说明还没判断这个点;当visited[i]值为1,说明当前的循环正在判断这个点;当visited[i]值为2,说明已经判断过这个点,含义是从这个点往后的所有路径都没有环,认为这个点是安全的。

那么,我们对每个点出发都做这个判断,检查这个点出发的所有路径上是否有环,如果判断过程中找到了当前的正在判断的路径,说明有环;找到了已经判断正常的点,说明往后都不可能存在环,所以认为当前的节点也是安全的。如果当前点是未知状态,那么先把当前点标记成正在访问状态,然后找后续的节点,直到找到安全的节点为止。最后如果到达了无路可走的状态,说明当前节点是安全的。

Code
时间复杂度 O(n),空间复杂度 O(n)

class Solution(object):
    def canFinish(self, N, prerequisites):
        """
        :type N,: int
        :type prerequisites: List[List[int]]
        :rtype: bool
        """
        graph = collections.defaultdict(list)
        for u, v in prerequisites:
            graph[u].append(v)
        # 0 = Unknown, 1 = visiting, 2 = visited
        visited = [0] * N
        for i in range(N):
            if not self.dfs(graph, visited, i):
                return False
        return True
        
    # Can we add node i to visited successfully?
    def dfs(self, graph, visited, i):
        if visited[i] == 1: return False
        if visited[i] == 2: return True
        visited[i] = 1
        for j in graph[i]:
            if not self.dfs(graph, visited, j):
                return False
        visited[i] = 2
        return True
        

Note:

  1. 新学了Python中collection的使用,其中尤其要注意 defaultdict(int) 以及 defaultdict(list) 的使用,其中defaultdict(int)可用于统计list中元素出现次数;而defaultdict(int)则可以简单看为 dict 的升级版,允许每个 key 下对应多个 value
    可简单参考如下:

    Using list as the default_factory, it is easy to group a sequence of key-value pairs into a dictionary of lists:

    >>> s = [('yellow', 1), ('blue', 2), ('yellow', 3), ('blue', 4), ('red', 1)]
    >>> d = defaultdict(list)
    >>> for k, v in s:
    ...     d[k].append(v)
    ...
    >>> d.items()
    [('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]
    

    When each key is encountered for the first time, it is not already in the mapping; so an entry is automatically created using the default_factory function which returns an empty list. The list.append() operation then attaches the value to the new list. When keys are encountered again, the look-up proceeds normally (returning the list for that key) and the list.append() operation adds another value to the list. This technique is simpler and faster than an equivalent technique using dict.setdefault():

    >>> d = {}
    >>> for k, v in s:
    ...     d.setdefault(k, []).append(v)
    ...
    >>> d.items()
    [('blue', [2, 4]), ('red', [1]), ('yellow', [1, 3])]
    

    Setting the default_factory to int makes the defaultdict useful for counting (like a bag or multiset in other languages):

    >>> s = 'mississippi'
    >>> d = defaultdict(int)
    >>> for k in s:
    ...     d[k] += 1
    ...
    >>> d.items()
    [('i', 4), ('p', 2), ('s', 4), ('m', 1)]
    

    When a letter is first encountered, it is missing from the mapping, so the default_factory function calls int() to supply a default count of zero. The increment operation then builds up the count for each letter.

    The function int() which always returns zero is just a special case of constant functions. A faster and more flexible way to create constant functions is to use itertools.repeat()which can supply any constant value (not just zero):

    >>> def constant_factory(value):
    ...     return itertools.repeat(value).next
    >>> d = defaultdict(constant_factory('<missing>'))
    >>> d.update(name='John', action='ran')
    >>> '%(name)s %(action)s to %(object)s' % d
    'John ran to <missing>'
    

    Setting the default_factory to set makes the defaultdict useful for building a dictionary of sets:

    >>> s = [('red', 1), ('blue', 2), ('red', 3), ('blue', 4), ('red', 1), ('blue', 4)]
    >>> d = defaultdict(set)
    >>> for k, v in s:
    ...     d[k].add(v)
    ...
    >>> d.items()
    [('blue', set([2, 4])), ('red', set([1, 3]))]
    

参考文献

[1] https://docs.python.org/2/library/collections.html#collections.defaultdict
[2] https://blog.csdn.net/fuxuemingzhu/article/details/82951771

猜你喜欢

转载自blog.csdn.net/Dby_freedom/article/details/84728673