OutputError: Find the longest path in a matrix with given constraints

OutputError: Find the longest path in a matrix with given constraints

我的矩阵是:

4 8 7 3 
2 5 9 3 
6 3 2 5 
4 4 1 6

问题(滑雪):

每个数字代表该山区的海拔

从网格中的每个区域(即方框),您可以向, west - 但前提是您要进入的区域的海拔高度比您所在区域的海拔高度

即你只能滑雪下坡。

您可以从地图上的任何地方开始,并且您正在寻找具有最长可能路径的起点,根据您访问的框数来衡量。

如果有几条相同长度的下行路径,您希望选择垂直落差最陡的那条,即起点海拔与终点海拔之间的最大差异高程。

我的解决方案:

def findSkiPath():
    mySolution = [0] * 3
    mySolution[0] = 0 # Distance
    mySolution[1] = 0 # Drop
    cellIndexRow = 0
    cellIndexCol = 0
    myPath = []
    myMatrix = [[4, 5, 8, 7],[1, 1, 5, 9], [0, 7, 5, 5], [7, 4, 2, 9]]
    countRows = len(myMatrix)
    countCols = len(myMatrix[0])
    for i in range(0, countRows - 1):
        for j in range(0, countCols - 1):
            myValue = myMatrix[i][j]
            myPath.append(myValue)

            #check east
            cellIndexRow = i
            cellIndexCol = j + 1
            checkAdjacentCells(cellIndexRow, cellIndexCol, myValue, myMatrix , mySolution , myPath )

            #check west
            cellIndexRow = i
            cellIndexCol = j - 1
            checkAdjacentCells(cellIndexRow, cellIndexCol, myValue, myMatrix , mySolution , myPath )

            #check north
            cellIndexRow = i - 1
            cellIndexCol = j
            checkAdjacentCells(cellIndexRow, cellIndexCol, myValue, myMatrix , mySolution , myPath )            

            #check south
            cellIndexRow = i + 1
            cellIndexCol = j
            checkAdjacentCells(cellIndexRow, cellIndexCol, myValue, myMatrix , mySolution , myPath )
    print (mySolution)

def checkAdjacentCells(cellIndexRow, cellIndexCol, myValue, myMatrix , mySolution , myPath ):

    #The base case - If we go beyond the limits of the matrix
    if (cellIndexRow < 0 or cellIndexRow > (len(myMatrix) - 1) or cellIndexCol < 0 or cellIndexCol > (len(myMatrix[0]) - 1)):
        evaluateSolution(mySolution , myPath )
        return

    #check if the next cell has a lower value than the current cell
    tmpValue = myMatrix[cellIndexRow][cellIndexCol]
    if tmpValue < myValue:
        newPath = myPath
        newPath.append(tmpValue)

        r = cellIndexRow
        c = cellIndexCol
        #check east
        cellIndexRow = r
        cellIndexCol = c + 1
        checkAdjacentCells(cellIndexRow, cellIndexCol, tmpValue, myMatrix , mySolution , newPath )

        #check west
        cellIndexRow = r
        cellIndexCol = c - 1
        checkAdjacentCells(cellIndexRow, cellIndexCol, tmpValue, myMatrix , mySolution , newPath )

        #check north
        cellIndexRow = r - 1
        cellIndexCol = c
        checkAdjacentCells(cellIndexRow, cellIndexCol, tmpValue, myMatrix , mySolution , newPath )          

        #check south
        cellIndexRow = r + 1
        cellIndexCol = c
        checkAdjacentCells(cellIndexRow, cellIndexCol, tmpValue, myMatrix , mySolution , newPath )

    evaluateSolution(mySolution , myPath )

def evaluateSolution(mySolution , myPath ):

    myDistance = 1
    mySolutionDistance = int(mySolution[0])
    mySolutionDrop = int(mySolution[1])

    if myDistance < mySolutionDistance:
        return

    myDrop = myPath[0] - myPath[-1]

    if myDistance > mySolutionDistance or myDrop > mySolutionDrop:
        mySolution[0] = myDistance
        mySolution[1] = mySolutionDrop
        mySolution[2] = myPath

if __name__ == "__main__":

    findSkiPath()

问题:

当前输出(距离、落差、路径):

[1, 0, [4, 2, 8, 7, 3, 4, 2, 5, 2, 3, 2, 1, 7, 3, 2, 5, 2, 3, 2, 1, 9, 3, 5, 2, 3, 2, 1, 7, 3, 2, 1, 6, 3, 2, 1, 2, 4, 3, 2, 1, 2, 1]]

预期输出:

[5,8,[9,5,3,2,1]]

在这个特定的地图上,最长的下行路径长度为 5,下降为 8 (9-1=8),路径为:9-5-3-2-1。

这样做的主要问题是您没有进行任何类型的回溯。您正在适当地遍历矩阵,但您没有做任何事情来维护特定路径的概念。相反,对于您访问的每个方块,您只需将所有合法移动附加到一个列表中。

相反,请考虑将当前路径保留为局部变量。对于给定方块的每个合法移动,您进行单独的递归调用以查找更多移动。每一个都会在 local 路径的末尾添加一个合法的移动。

每次调用 return 时,将找到的最佳路径与您保存的路径(迄今为止最好的)进行比较;保留这两者中较好的一个,然后进行下一个合法步骤。当您考虑了四个方向中的每一个时,return 调用实例的最知名路径。

在线和本网站上有很多回溯的例子;找出一些你觉得可以理解的。你可能会看硬币组合递归(找到一组增加到一定数量的硬币) - 它们不是相同的算法,但上面的回溯思路更清楚。

这是否促使您找到解决方案?

可以通过两种不同的方式解决问题中描述的挑战:

  1. 使用递归算法,如果满足给定要求,该算法仅遍历有效路径并在遍历矩阵元素时进行检查

  2. 分两步进行:

    2.1。通过简单调用 itertools 模块可用函数 permutations()

    获取所有可能路径的迭代器

    2.2。从生成的路径中选择满足要求的路径

第二种方法的代码更容易编写和理解,但是对于 4x4 大小的矩阵来说已经存在大量可能的路径,这使得它实际上不可能 运行 它用于更大的矩阵大小。

第一种方法的代码可以处理更大尺寸的矩阵,但缺点是更难掌握在其他约束的情况下如何调整它。

此处提出的问题 100% 1:1 重复 两年前提出的问题 here on Whosebug,标题为 "Maximum number of elements in the path of a matrix"。无论如何,这里再次给出了对那个老问题的回答的解决方案:

theMatrix = [
                [ 4, 8, 7, 3],
                [ 2, 5, 9, 3],
                [ 6, 3, 2, 5],
                [ 4, 4, 1, 6]
]

def longest_path(matrix):
    def inner_longest_path(x, y):
        best, best_path = 0, []
        # for all possible neighbor cells...
        for dx, dy in ((+1, 0), (-1, 0), (0, +1), (0, -1)):
            # if cell is valid and strictly smaller...
            if (0 <= x + dx < len(matrix) and 0 <= y + dy < len(matrix[x]) 
                    and matrix[x+dx][y+dy] < matrix[x][y]):
                n, path = inner_longest_path(x+dx, y+dy)  ### RECURSION
                # check if the path starting at that cell is better
                if n > best:
                    best, best_path = n, path
        return best + 1, [matrix[x][y]] + best_path

    return max(inner_longest_path(x, y) for x, row in enumerate(matrix) 
                                         for y, _ in enumerate(row))

print( longest_path(theMatrix) )

上面的代码打印:

(5, [9, 5, 3, 2, 1])

现在让我们也看一下 non-recursive 方法的代码,我自己在这里提供:

# myMatrix = [[4, 5, 8, 7],[1, 1, 5, 9], [0, 7, 5, 5], [7, 4, 2, 9]]
#  4 5 8 7
#  1 1 5 9 
#  0 7 5 5
#  7 4 2 9
myMatrix = [[4, 5, 8],[1, 1, 5], [0, 7, 5]]
#  4 5 8
#  1 1 5
#  0 7 5
# myMatrix = [[4, 5],[1, 1]]
#  4 5
#  1 1

def getAllValidSkiingPathsFrom(myMatrix): 
# def getDictRepresentationOf(myMatrix):
    dctOfMatrix = {}
    for row in range(len(myMatrix)):
        for column in range(len(myMatrix[0])):
            currPoint = (column, row)
            dctOfMatrix[currPoint] = myMatrix[row][column]

    lstIndicesOfAllMatrixPoints = list(dctOfMatrix.keys())
    setAllPossiblePaths = set()

    from itertools import permutations
    for pathCandidate in permutations(lstIndicesOfAllMatrixPoints): 
        lstPossiblePath = []
        prevIndexTuple = pathCandidate[0]
        lstPossiblePath.append(prevIndexTuple)
        for currIndexTuple in pathCandidate[1:]:
            if abs(currIndexTuple[0]-prevIndexTuple[0]) + abs(currIndexTuple[1]-prevIndexTuple[1]) > 1:
                break # current path indices not allowed in path (no diagonals or jumps)
            else:
                if dctOfMatrix[currIndexTuple] >= dctOfMatrix[prevIndexTuple]: 
                    break # only "down" is allowed for "skiing" 
                else:
                    lstPossiblePath.append(currIndexTuple)
                    prevIndexTuple = currIndexTuple
        if len(lstPossiblePath) > 1 and tuple(lstPossiblePath) not in setAllPossiblePaths: 
            setAllPossiblePaths.add(tuple(lstPossiblePath))

    return setAllPossiblePaths, dctOfMatrix
#:def getAllValidSkiingPathsFrom

setAllPossiblePaths, dctOfMatrix = getAllValidSkiingPathsFrom(myMatrix)

for path in setAllPossiblePaths:
    for point in path:
        print(dctOfMatrix[point], end=',')

这里是 myMatrix 的 2x2 和 3x3 版本的结果:

#  4 5
#  1 1
4,1,
5,1,
5,4,
5,4,1,

#   4 5 8
#   1 1 5
#   0 7 5
5,1,
8,5,1,
7,1,
4,1,
5,1,
5,4,
8,5,1,
1,0,
5,4,1,0,
8,5,4,1,
8,5,4,1,0,
8,5,4,
8,5,
7,0,
7,5,
8,5,
4,1,0,
5,4,1,

我希望代码是self-explanatory,但如果不是这里粗略的想法:

  1. 构建一个表示矩阵的字典,其中键是(列,行)"coordinates" 的元组,值是矩阵中的值。

  2. 构建矩阵内所有可能完整路径的列表(排列)

  3. 过滤所有可能的完整路径列表,根据需要(应用标准)仅提取有效路径。

我没有 运行 计算非常昂贵的 4x4 矩阵结果计算,因为它在我的盒子上肯定需要几分钟以上的时间。

为了完整起见,我想提一下还有另一个问题 HERE on Whosebug,它是这个问题的变体(它对有效路径有一些其他规则,并要求能够工作的算法在不规则矩阵上)。