【问题标题】:Max path sum in a 2D array二维数组中的最大路径和
【发布时间】:2017-08-08 16:20:10
【问题描述】:

考虑以下问题:

给定一个无符号整数的二维数组和最大长度 n,在该矩阵中找到一个不超过 n路径 并且使总和最大化。输出应包含路径和总和。

路径由相邻的整数组成,这些整数要么都在同一行,要么在同一列,要么在右下方向的对角线上。

例如,考虑以下矩阵和给定路径长度限制3

 1  2  3  4  5    
 2  1  2  2  1   
 3  4  5* 6  5    
 3  3  5 10* 5    
 1  2  5  7 15* 

最佳路径是5 + 10 + 15(节点标有*)。

现在,在看到这个问题时,动态编程解决方案似乎是最合适的,因为这个问题与Min Cost PathMaximum Sum Rectangular Submatrix 等其他问题相似。问题在于,为了正确解决这个问题,您需要从矩阵中的每个整数(节点)开始构建路径,而不仅仅是从左上角开始到右下角结束。

我最初正在考虑一种类似于Maximum Sum Rectangular Submatrix 解决方案的方法,其中我可以存储来自每个节点的每个可能路径(路径长度小于n,只会向右/ down),但我能想到的唯一方法是从每个节点递归调用 down 和 right,这似乎违背了 DP 的目的。另外,我需要能够存储最大路径。

我正在考虑的另一个可能的解决方案是以某种方式调整最长路径搜索并从图中的每个 int 运行它,其中每个 int 就像一个边权重。

找到最大路径的最有效方法是什么?

【问题讨论】:

  • 你能解释一下这个例子吗? 10 和 15 不是邻居,那么10 + 15 怎么会在路径中呢?另外,您说路径可以从 任何 int 到任何其他 int,那么您的示例中的那些 int 是什么——它们是给定的,还是应该是输出的一部分?这些整数是由值还是由(行、列)索引给出的?
  • 你写“通过向下或向右”,但提出的解决方案有一个对角线台阶。那么您的意思是“向下,向右,或对角线向下”
  • 矩阵可以有负数吗?
  • @trincot 是的,我的意思是向下,或向右,或对角线向下,而且矩阵不能有负数
  • @trincot 实际上,我似乎误读了这个问题。约束是路径只能选择同一行、同一列或对角线下方的整数。

标签: arrays algorithm recursion dynamic-programming


【解决方案1】:

这里的挑战是避免多次对相同的节点求和。为此,您可以应用以下算法:

算法

  1. 对于 3 个方向(下、下+右、右)中的每一个,执行步骤 2 和 3:

  2. 确定该方向上存在的行数。对于向下方向,这是列数。对于向右的方向,这是行数。对于对角线方向,这是对角线的数量,即行数和列数之和减1,如下图红线所示:

  1. 对每一行做:

    • 确定该行的第一个节点(称为“头”),并将“尾”设置为同一节点。这两个引用指的是“当前”路径的端点。同时将总和和路径长度设置为零。

    • 对于当前行的每个头节点,执行以下要点:

      • 将头节点的值加到总和中并增加路径长度

      • 如果路径长度大于允许的最大值,则从总和中减去尾部的值,并将尾部设置为当前行上跟随它的节点

      • 只要总和大于目前找到的最大总和,请将其与路径的位置一起记住。

      • 将head设置为当前行上跟在它后面的节点

最后返回最大的和产生这个和的路径。

代码

这是一个基本 JavaScript 的实现:

function maxPathSum(matrix, maxLen) {
    var row, rows, col, cols, line, lines, dir, dirs, len,
        headRow, headCol, tailRow, tailCol, sum, maxSum;

    rows = matrix.length;
    cols = matrix[0].length;
    maxSum = -1;
    dirs = 3; // Number of directions that paths can follow
    if (maxLen == 1 || cols == 1) 
        dirs = 1; // Only need to check downward directions

    for (dir = 1; dir <= 3; dir++) {
        // Number of lines in this direction to try paths on
        lines = [cols, rows, rows + cols - 1][dir-1];
        for (line = 0; line < lines; line++) {
            sum = 0;
            len = 0;
            // Set starting point depending on the direction
            headRow = [0, line, line >= rows ? 0 : line][dir-1];
            headCol = [line, 0, line >= rows ? line - rows : 0][dir-1];
            tailRow = headRow;
            tailCol = headCol;
            // Traverse this line
            while (headRow < rows && headCol < cols) {
                // Lengthen the path at the head
                sum += matrix[headRow][headCol];
                len++;
                if (len > maxLen) {
                    // Shorten the path at the tail
                    sum -= matrix[tailRow][tailCol];
                    tailRow += dir % 2;
                    tailCol += dir >> 1;
                }
                if (sum > maxSum) {
                    // Found a better path
                    maxSum = sum;
                    path = '(' + tailRow + ',' + tailCol + ') - ' 
                         + '(' + headRow + ',' + headCol + ')';
                }
                headRow += dir % 2;
                headCol += dir >> 1;
            }
        }
    }
    // Return the maximum sum and the string representation of
    // the path that has this sum
    return { maxSum, path };
} 

// Sample input
var matrix = [
    [1,  2,  3,  4,  5],
    [2,  1,  2,  2,  1],
    [3,  4,  5,  5,  5],
    [3,  3,  5, 10,  5],
    [1,  2,  5,  5, 15],
];

var best = maxPathSum(matrix, 3);

console.log(best);

关于代码的一些细节

  1. 注意行/列索引从 0 开始。

  2. 头尾坐标的递增方式是基于dir变量的二进制表示:它采用这三个值(二进制表示法):01、10、11

    然后您可以使用第一位来指示该方向上的下一步是否在下一列 (1) 上或不在 (0) 上,第二位指示它是否在下一行 (1) 或不是(0)。你可以这样描述它,其中 00 代表“当前”节点:

    00 10
    01 11
    

    所以我们对dir的值有这个含义:

    • 01:沿着柱子走
    • 10:沿着那一排走
    • 11:斜着走

    代码使用&gt;&gt;1 提取第一位,% 2 提取最后一位。该操作在两种情况下都会产生 0 或 1,并且是需要添加到列或行的值。

  3. 以下表达式创建一维数组并即时获取其中一个值:

    headRow = [0, line, line >= rows ? 0 : line][dir-1];
    

    简称:

    switch (dir) {
    case 1:
        headRow = 0;
        break;
    case 2:
        headRow = line;
        break;
    case 3:
        if (line >= rows) 
            headRow = 0
        else 
            headRow = line;
        break;  
    }
    

时间和空间复杂度

head 将在每个方向上仅访问每个节点一次。尾部将访问更少的节点。方向数不变,最大路径长度值不影响head访问次数,所以时间复杂度为:

        Θ(行*列)

在这个算法中没有使用额外的数组,只有几个原始变量。所以额外的空间复杂度是:

        Θ(1)

两者都是你所希望的最好的。

是动态规划吗?

在 DP 解决方案中,您通常会使用某种表格或记忆,可能以矩阵的形式,其中为特定节点找到的每个子结果都被输入以确定相邻节点的结果。

这样的解决方案可能需要 Θ(rows*columns) 额外的空间。但是这个问题可以在没有这种(广泛的)空间使用的情况下解决。当一次查看一条时(一行、一列或一条对角线),该算法与 Kadane 的算法有一些相似之处:

一个不同之处在于,此处扩展或缩短路径/子数组的选择不取决于矩阵数据本身,而是取决于给定的最大长度。这也与这里所有值都保证为非负的事实有关,而 Kadane 的算法适用于有符号数。

就像 Kadane 的算法一样,迄今为止最好的解决方案都保存在一个单独的变量中。

另一个区别是这里我们需要从三个方向看。但这只是意味着在这三个方向上重复相同的算法,同时沿用目前找到的最佳解决方案。

这是动态编程的一个非常基本的用法,因为这里不需要制表或记忆技术。我们只在变量 summaxSum 中保留最佳结果。这不能被视为制表或记忆,它们通常会跟踪必须在某个时间比较的几个竞争结果。见this interesting answer on the subject

【讨论】:

  • 感谢您的详细回答! +1 用于插图和代码。我不明白的一件事是这样的行在做什么:lines = [cols, rows, rows + cols - 1][dir-1] 似乎您正在这些行中初始化一个二维矩阵,但lines 后来被用作数字而不是集合。是这样吗?
  • 第一对方括号创建一个一维数组,第二对从该数组中获取一个元素,所以它就像一个浓缩的switch 语句,用于为lines 赋值,具体取决于dir 的值。它也可以用两个三元运算符编写。
  • 看起来该解决方案有效,但仍将其视为动态编程。我没有看到任何正在构建的表或正在使用的记忆。它的 DP 部分是否仅仅是因为我们记住了总和最大的路径这一事实?而且我假设当前路径的加长和缩短本质上是在模仿 Kadane 算法的功能?
  • 我在答案的中间添加了对此的解释,在标题“关于代码的一些细节”下,第 2 点。
  • 我不知道,...我想经过 30 多年的编程,人们会建立某种直觉。
【解决方案2】:

使用 F[i][j][k] 作为最大路径总和,其中路径长度为 k 并在位置 (i, j) 处结束。

F[i][j][k] 可以从 F[i-1][j][k-1] 和 F[i][j-1][k-1] 计算出来。

答案将是 F 的最大值。

要检索最大路径,使用另一个表G[i][j][k]来存储F[i][j][k]的最后一步,即来自(i-1,j)或 (i,j-1)。

【讨论】:

    【解决方案3】:

    限制是只能通过在矩阵中向下或向右创建路径。

    解复杂度O(N * M * L) 其中:

    • N:行数
    • M:列数
    • L:路径的最大长度

      int solve(int x, int y, int l) { 
        if(x > N || y > M) { return -INF; } 
        if(l == 1) {matrix[x][y];}
        if(dp[x][y][l] != -INF) {return dp[x][y][l];} // if cached before, return the answer 
        int option1 = solve(x+1, y, l-1); // take a step down 
        int option2 = solve(x, y+1, l-1); // take a step right 
        maxPath [x][n][l] = (option1 > option2 ) ? DOWN : RIGHT; // to trace the path 
        return dp[x][y][l] = max(option1, option2) + matrix[x][y];
      }
      

    示例:solve(3,3,3):从 (3,3) 开始的最大路径总和,长度为 3(2 步)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-05-21
      • 2020-03-19
      • 2020-09-19
      • 1970-01-01
      • 1970-01-01
      • 2013-04-22
      • 2020-11-24
      • 1970-01-01
      相关资源
      最近更新 更多