【拓撲排序】包含拓撲深度的拓撲排序 LeetCode 329

0x01 題面

LeetCode 329. Longest Increasing Path in a Matrix 傳送門

Given an integer matrix, find the length of the longest increasing path.

From each cell, you can either move to four directions: left, right, up or down. You may NOT move diagonally or move outside of the boundary (i.e. wrap-around is not allowed).

# Example 1
nums = [
  [9,9,4],
  [6,6,8],
  [2,1,1]
]
# Return 4
# The longest increasing path is [1, 2, 6, 9].

# Example 2:
nums = [
  [3,4,5],
  [3,2,6],
  [2,2,1]
]
# Return 4
# The longest increasing path is [3, 4, 5, 6]. Moving diagonally is not allowed.

Credits:
Special thanks to @dietpepsi for adding this problem and creating all test cases.

0x02 思路提示

第一眼很自然的反應應該是DFS或者BFS,
前者每次到達終點時記錄路徑長度,遍歷完所有路線後取最大,
後者記錄遍歷深度,深度即最大長度,
但是,肯定會倒在一個遞歸深度的測試數據面前,
就算不刻意卡深度,上由於多種編譯器在棧深度的限制都會導致stack overflow,
所以尋找非遞歸的解決方案纔是關鍵,那麼就是拓撲排序了,
這道題難就難在想到這一點(吹,接着吹,這題的Tag裏就寫了拓撲排序的好吧),
那麼,怎麼把這個矩陣走格子的問題變成圖,來使用圖論裏的拓撲排序呢?
矩陣本身就是一張鄰接圖呀,
那麼拓撲排序之後該如何獲取最長路徑的長度呢?
這題只需要長度,如果需要返回值是路徑經過的節點又該怎麼處理呢?
提示就到這裏,去試試看吧。

0x03 解題源碼

自寫代碼 Accepted

class Solution:
    def longestIncreasingPath(self, matrix):
        """
        :type matrix: List[List[int]]
        :rtype: int
        """

        def topsort(G):
            in_degrees = dict((u, 0) for u in G)
            topo_level = dict((u, 0) for u in G)
            for u in G:
                for v in G[u]:
                    in_degrees[v] += 1  # each node's indegree
            S, Q = [], [u for u in G if in_degrees[u] == 0]  # node with indegree 0, get level=0
            while Q:
                u = Q.pop()  # Default remove the end
                S.append(u)
                for v in G[u]:
                    in_degrees[v] -= 1  # Remove this link
                    topo_level[v] = max(topo_level[v], topo_level[u]+1)
                    if in_degrees[v] == 0:
                        Q.append(v)
            return S, list(map(lambda x: topo_level[x], S))

        G = {}  # G means graph
        nodes, links = [], []  # prepare nodes and links for toposort
        row = matrix.__len__()
        if not row: return 0
        column = matrix[0].__len__()
        for j in range(column)[::-1]:
            for i in range(row)[::-1]:
                node_value = matrix[i][j]
                cur_node = "{},{}".format(i,j)
                down_node = "{},{}".format(i+1,j) if i+1<row else None
                right_node = "{},{}".format(i,j+1) if j+1<column else None
                # print(cur_node, down_node, right_node)

                # add nodes
                G[cur_node] = []
                nodes.append(cur_node)

                # then add links
                if down_node:
                    down_value = matrix[i+1][j]
                    if node_value > down_value:
                        links.append((down_node, cur_node))
                        G[down_node].append(cur_node)
                    if down_value > node_value:
                        links.append((cur_node, down_node))
                        G[cur_node].append(down_node)
                if right_node:
                    right_value = matrix[i][j+1]
                    if node_value > right_value:
                        links.append((right_node, cur_node))
                        G[right_node].append(cur_node)
                    if right_value > node_value:
                        links.append((cur_node, right_node))
                        G[cur_node].append(right_node)

        result, topo_level = topsort(G)
        return max(topo_level)+1


s = Solution()
nums = [
  [9,9,4],
  [6,6,8],
  [2,1,1]
]
print(s.longestIncreasingPath(nums))

0x04 解題報告

獲取圖節點

node_value = matrix[i][j]
cur_node = "{},{}".format(i,j)
down_node = "{},{}".format(i+1,j) if i+1<row else None
right_node = "{},{}".format(i,j+1) if j+1<column else None
# print(cur_node, down_node, right_node)

# add nodes
G[cur_node] = []
nodes.append(cur_node)

獲取有向邊

這裏我採用了從右下向左上遍歷節點,每個節點只和右方、下方連邊的方式,
來保證不遺漏、不重複:

# then add links
if down_node:
    down_value = matrix[i+1][j]
    if node_value > down_value:
        links.append((down_node, cur_node))
        G[down_node].append(cur_node)
    if down_value > node_value:
        links.append((cur_node, down_node))
        G[cur_node].append(down_node)
if right_node:
    right_value = matrix[i][j+1]
    if node_value > right_value:
        links.append((right_node, cur_node))
        G[right_node].append(cur_node)
    if right_value > node_value:
        links.append((cur_node, right_node))
        G[cur_node].append(right_node)

實現拓撲排序

並在其基礎上魔改兩行,增加深度標記:

 def topsort(G):
      in_degrees = dict((u, 0) for u in G)
      topo_level = dict((u, 0) for u in G)  # depth level for toposort
      for u in G:
          for v in G[u]:
              in_degrees[v] += 1  # each node's indegree
      S, Q = [], [u for u in G if in_degrees[u] == 0]  # node with indegree 0, get level=0
      while Q:
          u = Q.pop()  # Default remove the end
          S.append(u)
          for v in G[u]:
              in_degrees[v] -= 1  # Remove this link
              topo_level[v] = max(topo_level[v], topo_level[u]+1)  # IMPORTANT MODIFIED POINT
              if in_degrees[v] == 0:
                  Q.append(v)
      return S, list(map(lambda x: topo_level[x], S))

調用

獲得拓撲序及深度標記,取最大深度,
由於我的深度是從0開始的所以需要加1:

result, topo_level = topsort(G)
return max(topo_level)+1

0x05 後記

這裏我特意只使用了拓撲深度來解決題目中需要的長度需求,
但輸出的時候我僅僅使用了第二個返回值,
那麼第一個返回值(拓撲序節點列表)有什麼用處呢?
是否可以與第二個返回值(節點拓撲深度列表)合作,
從而得到最長路徑經歷的節點順序呢?
試試看吧~

0x06 應用場景下拓撲排序的使用案例

對於具有單向連邊關係的節點,需要按照拓撲排序生成滿足非嚴格拓撲升序唯一性的節點序列(並重排節點名稱)

def sort_elements_in_answer(dic):
    """
    sort relations in one candidate_answer
    :param dic: candidate_answer as a dict {'M0':{...}, 'M1':{...}}
    :return: a dict with relations ordered
    """

    def cd_topo_sort(G):
        """
        topo_sort implement recently as
        https://blog.csdn.net/okcd00/article/details/79446956
        """
        in_degrees = dict((u, 0) for u in G)
        topo_level = dict((u, 0) for u in G)  # depth level for toposort
        for u in G:
            for v in G[u]:
                in_degrees[v] += 1  # each node's indegree
        S, Q = [], [u for u in G if in_degrees[u] == 0]  # node with indegree 0, get level=0
        while Q:
            u = Q.pop()  # Default remove the end
            S.append(u)
            for v in G[u]:
                in_degrees[v] -= 1  # Remove this link
                topo_level[v] = max(topo_level[v], topo_level[u] + 1)  # IMPORTANT MODIFIED POINT
                if in_degrees[v] == 0:
                    Q.append(v)

        _order = lambda x: topo_level[x]
        return S, list(map(lambda x: (_order(x), x), S))

    if 'Data' in dic:
        dic['Data'] = sort_elements_in_answer(dic.get('Data'))
        return dic

    dic = dict(map(
        lambda x: (x.get('Name'), x),
        sorted(dic.values(), key=lambda x: x.get('Name'))))

    graph = dict([(x, []) for x in dic.keys()])
    for k, v in sorted(dic.items()):
        cause = v.get('C', None)
        if cause and cause.startswith('M'):
            graph[cause].append(k)

    _, topo_list = cd_topo_sort(graph)

    def order(x):
        _node = dic[x]
        _ret = _node.get('R')
        while _node.get('C').startswith('M'):
            _node = dic[_node.get('C')]
            _ret = _ret + _node.get('R')
        # print _node.get('C') + _ret
        return _node.get('C') + _ret

    topo_list.sort(key=lambda x: (x[0], order(x[1])))

    n_len = topo_list.__len__()
    reorder_dict = dict(zip(  # old_node -> new_node
        [topo_list[idx][1] for idx in range(n_len)],
        ["M{}".format(idx) for idx in range(n_len)]))
    reflect_dict = dict([(v, k) for (k, v) in reorder_dict.items()])

    def gen_new_node(idx):
        new_node = "M{}".format(idx)
        old_node = reflect_dict.get(new_node)
        _ret = dic[old_node]
        if _ret.get('C') is None:
            return None
        _ret['C'] = reorder_dict.get(_ret['C'], _ret['C'])
        _ret['Name'] = new_node
        return _ret

    return dict(zip(
        ["M{}".format(idx) for idx in range(n_len)],
        [gen_new_node(idx) for idx in range(n_len)]))


def sort_elements_in_answers(answers):
    return [sort_elements_in_answer(each) for each in
            map(lambda x: x.get('answer', {}), answers)]

0xFF 呱代碼

Accepted @ 2018-03-05

class Solution:
    def longestIncreasingPath(self, matrix):
        """
        :type matrix: List[List[int]]
        :rtype: int
        """
        def getIndegree(matrix, i, j):
            result = 0
            if i - 1 >= 0 and matrix[i][j] < matrix[i - 1][j]:
                result += 1

            if i + 1 < row and matrix[i][j] < matrix[i + 1][j]:
                result += 1

            if j - 1 >= 0 and matrix[i][j] < matrix[i][j - 1]:
                result += 1

            if j + 1 < col and matrix[i][j] < matrix[i][j + 1]:
                result += 1

            return result

        def delete(point, matrix, shadow_matrix):
            i = point[0]
            j = point[1]
            result = []
            if i - 1 >= 0 and matrix[i][j] > matrix[i - 1][j]:
                shadow_matrix[i-1][j] -= 1
                if shadow_matrix[i-1][j] == 0:
                    result.append([i-1, j])

            if i + 1 < row and matrix[i][j] > matrix[i + 1][j]:
                shadow_matrix[i+1][j] -= 1
                if shadow_matrix[i+1][j] == 0:
                    result.append([i+1, j])

            if j - 1 >= 0 and matrix[i][j] > matrix[i][j - 1]:
                shadow_matrix[i][j-1] -= 1
                if shadow_matrix[i][j-1] == 0:
                    result.append([i, j-1])

            if j + 1 < col and matrix[i][j] > matrix[i][j + 1]:
                shadow_matrix[i][j+1] -= 1
                if shadow_matrix[i][j+1] == 0:
                    result.append([i, j+1])
            return result

        if len(matrix) == 0 or len(matrix[0]) == 0:
            return 0

        shadow_matrix = []
        queue = []
        paths = {}
        row = len(matrix)
        col = len(matrix[0])

        # compute the indegree of each point in the matrix
        for i in range(row):
            shadow_matrix.append([])
            for j in range(col):
                indegree = getIndegree(matrix, i, j)
                shadow_matrix[i].append(indegree)
                if indegree == 0:
                    queue.append([i, j])
                    paths[str([i, j])] = 1

        # topsort:
        # - delete the points of zere indegree and the out edge
        # - add the points effected by delete operation into the queue
        # - repeate until the queue is empty
        while len(queue) != 0:
            point = queue.pop(0)
            effected_points = delete(point, matrix, shadow_matrix)
            if len(effected_points) != 0:
                queue += effected_points
                for ep in effected_points:
                    paths[str(ep)] = paths[str(point)] + 1


        # find maxium path len
        max = 1
        for p in paths:
            if paths[p] > max:
                max = paths[p]
        return max
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章