【问题标题】:Finding the optimal path in a grid在网格中找到最佳路径
【发布时间】:2020-01-11 17:39:01
【问题描述】:

背景 今天面试,被问到以下问题。

给你一个网格。

  int [][] grid =
                {
                {0,0,0,2,5},
                {0,0,0,1,5},
                {0,1,1,1,1},
                {2,0,0,0,0}
                };

您从网格的左下角开始。您只能向上向右。我们的想法是到达右上角。你要走能让你获得最大价值的道路。

上面的输出是 16

我的解决方案

public static int getPathMaxSum(int [][] grid){
    int row = grid.length-1;
    int col = 0;
    int currentSum = grid[row][col];
    return getMax(grid,currentSum,row,col);
}

public static int getMax(int [][] grid,int sum,int row,int col){
    if((isTopRight(grid,row,col)) || (!isValid(grid,row,col))){
        return sum;
    }else{
        sum = sum + grid[row][col];
        return Math.max(getMax(grid,sum,row-1,col),getMax(grid,sum,row,col+1));
    }
}

public static boolean isTopRight(int [][] grid, int row, int col){
    return row == 0 && col == grid[row].length-1;
}

public static boolean isValid(int [][] grid, int row, int col){
    return (row >= 0 &&  row < grid.length) && (col >= 0 && col < grid[row].length);
}

我正在尝试递归解决这个问题。我认为我在每个位置都有 2 个可能的选择,我想获得这两个选择中的最大值,但由于某种原因我无法获得正确的输出。

我有两个辅助函数来检查我的位置是否在网格内的有效含义以及我是否点击了右上角,如果我点击了我的基本情况。

我很想看看我哪里出错了。

【问题讨论】:

  • getMax 返回一个int。但是这两个递归调用对返回的总和没有任何作用。
  • 您应该选择两个递归调用中的最大值,然后将其添加到您的currSum,然后最后返回 - 所以return currSum + Math.max( getMax(grid, sum, row+1, col), getMax(grid, sum, row, col+1) );
  • @SomeDude 是否可以将其添加为答案
  • @Dinero 我的建议解决了你的问题吗?
  • @SomeDude 我实际上尝试了你的建议,但它不起作用。

标签: java algorithm recursion dynamic-programming backtracking


【解决方案1】:

您的方法中不需要sum 参数。

我假设您已经了解如何使用递归自上而下的方法解决此问题。 但是为了完整起见,基本公式是:

您从row 处的一个单元格开始,col 获取其值,然后您可以查看 UP (row-1, col) 或 RIGHT (row, col+1)

所以结果将是:

grid[row][col] + Math.max( getMax( row-1, col, grid ), getMax( row, col+1, grid ) )

基本条件:

a) 如果它位于右上角,即目的地,则无需递归,只需返回该单元格的值即可。

b) 如果它是像您在 isValid 方法中编写的无效单元格,则需要返回 Integer.MIN_VALUE,因为您的其他单元格中可能有负值,并且您希望它们最大。

所以你的getMax 函数需要是:

public static int getMax(int [][] grid,int row,int col){
    if (isTopRight(grid,row,col)) {
       return grid[row][col];
    } else if (!isValid(grid,row,col)){
        return Integer.MIN_VALUE;
    } else {
        return grid[row][col] + Math.max(getMax(grid,row-1,col),getMax(grid,row,col+1));
    }
}

你可以看到工作示例here

【讨论】:

    【解决方案2】:

    编辑:回答您的代码的编辑版本

    您的新解决方案存在的问题:

    1. int currentSum = grid[row][col];sum = sum + grid[row][col];

      总和用左下角的值初始化,在getMax()的初始调用中再次添加相同的值。这不是应该的样子。求和以 0 开头,加法由getMax() 完成。

    2. if((isTopRight(grid,row,col)) || (!isValid(grid,row,col))) 然后return sum;

      对于无效位置,这将起作用(请参阅我的代码下方的限制),但不适用于右上角(因为我们尚未添加角的值)。因此,将这两个条件分开,只在无效位置上直接返回。在任何其他位置上,首先添加值,然后,如果您达到“目标”,则返回总和。否则返回“向右”和“向上”的最大值(递归调用现在正确)。

    解决这些问题并实施您的示例,我得出以下代码:

    public class Pathfinder {
    
        public static void main(String... args) {
            int [][] grid = {
                {0,0,0,2,5},
                {0,0,0,1,5},
                {0,1,1,1,1},
                {2,0,0,0,0}
            };
            
            System.out.println(getPathMaxSum(grid));
        }
    
        public static int getPathMaxSum(int[][] grid) {
            int row = grid.length - 1;
            int col = 0;
            
            return getMax(grid, 0, row, col);
        }
    
        public static int getMax(int[][] grid, int sum, int row, int col) {
            if(!isValid(grid, row, col))
                return sum;
            
            sum = sum + grid[row][col];
            
            if(isTopRight(grid, row, col))
                return sum;
            
            return Math.max(getMax(grid, sum, row - 1, col), getMax(grid, sum, row, col + 1));
        }
    
        public static boolean isTopRight(int[][] grid, int row, int col) {
            return row == 0 && col == grid[row].length - 1;
        }
    
        public static boolean isValid(int[][] grid, int row, int col) {
            return (row >= 0 &&  row < grid.length) && (col >= 0 && col < grid[row].length);
        }
    }
    

    请注意,如果所有条目都是非负数,则此版本适用于任何网格(只要堆栈足够大并且我们不处理太大的数字,例如我们不会出现任何整数溢出)。无论如何,可以以这样的方式操作具有负条目的网格,该算法将找到最佳路径,并且可以轻松地将解决方案“转换”回原始网格(只需从每个条目中减去最小值)。

    原始代码的答案

    我发现您的代码存在多个问题:

    1. isValid(grid,row+1,col)sum1 = grid[row+1][col];

      您正在尝试添加 1 到该行,但您(正确地)从int row = grid.length-1; 开始。添加 1 会给你一个无效的位置,因此第一个分支将永远不会被执行。相反,您需要从该行中减去 1 才能“向上”。

    2. sum = sum + Math.max(sum1,sum2);

      这改变了sum,但你看不到你移动的方向。然后直接...

    3. getMax(grid,sum,row+1,col);getMax(grid,sum,row,col+1);

      ...您使用新的最大总和进行递归调用,但来自两个位置。要获得正确的解决方案,您应该使用值调用它们,它们的方向代表。另请注意,此处row+1 也需要替换为row-1

    4. return sum;

      您现在返回这个最大总和,但完全忽略了递归调用的返回。相反,您应该比较它们的回报,并为自己返回两者的更高价值。

    5. 回溯与动态规划

      您的算法应该可以正常工作,并且足以解决问题的小实例,但不适用于较大的问题(因为它会为每一步进行 2 次递归调用,并且您有 2*(n-1) 步......导致指数运行时间)。二次运行时的另一种方法是向后遍历该字段并通​​过仅向右或向上查看一个字段并将当前字段的值添加到该字段的最大值来选择最佳方式。只需从右上角开始向左走,从右到左逐行。

    【讨论】:

    • 是否可以编辑我现有的解决方案并将其作为答案发布,以便我接受。
    • 是的。我稍后会这样做。我会坚持回溯并建议至少考虑动态方法。
    • 我刚刚发布了仍然无法正常工作的代码编辑版本。我了解动态方法的工作原理,但我首先想澄清我的递归方法。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-12-31
    • 1970-01-01
    • 2015-04-08
    • 2018-03-18
    • 2012-10-14
    • 1970-01-01
    相关资源
    最近更新 更多