为啥在这种情况下只有回溯有效?
Posted
技术标签:
【中文标题】为啥在这种情况下只有回溯有效?【英文标题】:Why does only backtracking work in this scenario?为什么在这种情况下只有回溯有效? 【发布时间】:2020-08-12 03:54:32 【问题描述】:我正在LeetCode.com解决这个问题:
在大小为 m * n 的金矿网格中,该矿井中的每个单元格都有一个整数表示该单元格中的黄金数量,如果为空则为 0。返回您在以下条件下可以收集的最大金币数量:- (a) 每次您位于一个牢房中时,您都会收集该牢房中的所有金币; (b) 从您的位置,您可以向左、向右、向上或向下走一步。 (c) 您不能多次访问同一个牢房; (d) 永远不要访问 0 金币的牢房。 (e) 您可以从网格中任何有黄金的位置开始和停止收集黄金。 对于网格:
[[0,6,0],[5,8,7],[0,9,0]]
,输出为:24
。
我写了下面的代码:
class Solution
public:
int dig(vector<vector<int>>& grid, int i, int j)
if(i>=grid.size() || i<0 || j>=grid[0].size() || j<0 || grid[i][j]==0) return 0;
//change begins...
int gold=0;
gold+=grid[i][j];
grid[i][j]=0;
gold+=max(dig(grid, i+1, j), max(dig(grid, i, j+1), max(dig(grid, i-1, j), dig(grid, i, j-1))));
return gold;
//change ends...
int getMaximumGold(vector<vector<int>>& grid)
vector<vector<int>> gridCopy=grid;
int maxGold=0;
for(int i=0; i<grid.size(); i++)
for(int j=0; j<grid[0].size(); j++)
if(grid[i][j]!=0)
maxGold=max(maxGold, dig(gridCopy, i, j));
gridCopy=grid;
return maxGold;
;
但是,它在输入 [[1,0,7,0,0,0],[2,0,6,0,1,0],[3,5,6,7,4,2],[4,3,1,0,2,0],[3,0,5,0,20,0]]
时中断;产生58
而不是60
。
我发现了另一个代码here,它是相同的,除了在上面的注释部分,它们有以下几行:
g[i][j] = -g[i][j];
auto res = max( dfs(g, i + 1, j), dfs(g, i, j + 1), dfs(g, i - 1, j), dfs(g, i, j - 1) );
g[i][j] = -g[i][j];
return g[i][j] + res;
(当然,他们不会在嵌套的 for 循环中将 grid
分配给 gridCopy
,因为他们会将修改后的网格恢复为其原始形式)。
我知道他们在回溯,而我没有。但我无法理解我做错了什么,因为从逻辑上讲,我正在做同样的事情。我使用调试语句来跟踪问题,但由于有很多递归调用,因此很难跟进。
有人可以指出我上面的代码中的逻辑谬误是什么吗?
谢谢!
【问题讨论】:
你有输入、算法和动机。当这里的人在戳你给出的内容时,在调试器中运行你的代码,看看你是否能在他们发现问题之前发现问题。 是的,这就是我现在正在尝试的。谢谢,@ user4581301。 :) 【参考方案1】:您所有的递归调用都可能会修改网格,并且不会恢复它。 这意味着对单元的第一次访问将阻止它进行所有其他后续尝试。
例如,如果首先评估 dig(grid, i+1, j)
,那么当您执行其他三个方向时,在该计算期间访问的所有单元格都不可用。
如果您的代码恰好从左上角开始并在您的示例中先向下,您将访问 1-2-3-4-3 并卡住。 在那次步行之后,从左上角开始就再也没有路径了,尽管从一开始就有很多路径,其中一些支付的费用远远超过 13。 (你可能认为你会从任一方向找到路径,但这取决于评估顺序。)
共享可变状态和递归是一个非常棘手的组合。
【讨论】:
"...您所有的递归调用都可能修改网格..." 那么,这不应该是故意的吗?基于规则 (c),我不想再重蹈覆辙。 嗯,那么链接代码有什么不同呢?grid[i][j]=-grid[i][j]
不应该导致相同的情况吗?此更改是在递归调用(以及后续恢复)之前进行的。
一次“步行”期间您不能多次访问它,但有很多可能的步行。
能否详细说明?【参考方案2】:
您的代码中似乎有一点错误是:
//change begins...
int gold=0;
gold+=grid[i][j];
grid[i][j]=0;
gold+=max(dig(grid, i+1, j), max(dig(grid, i, j+1), max(dig(grid, i-1, j), dig(grid, i, j-1))));
return gold;
//change ends...
在这里,您更改了 grid[i][j]
的值,并且更改的值正在影响您的输入,即您的输入集现在是错误的。由于grid[i][j]
的值已更改,因此这将影响其余的计算。
您可以做的是将grid[i][j]
的初始值存储在该递归堆栈中的某个位置,并在您探索完该节点的所有路径后重新分配回grid[i][j]
。
例如:对你的逻辑稍作修改
//change begins...
int gold=0;
gold+=grid[i][j];
int temp = grid[i][j];
grid[i][j]=0;
gold+=max(dig(grid, i+1, j), max(dig(grid, i, j+1), max(dig(grid, i-1, j), dig(grid, i, j-1))));
grid[i][j] = temp;
return gold;
//change ends...
如果您在问题中使用解决方案,您还可以在递归堆栈中创建内存。
只是为了回答你的问题,为什么只有回溯才能解决这个问题:
您需要了解您的解决方案,如下所示:
查看每个网格项是否可以成为解决方案集的一部分。 为此,您选择一个网格项(其中项值应大于 0) 然后你从那个项目探索所有环境。使用 DFS,您可以继续探索他们的周围环境等,直到满足退出条件。 现在,在您的解决方案中,您已经改变了 grid[i][j] 的值(为了便于理解,假设 grid[2][3] 发生了突变)当且仅当该项目被选中时才可以存在于最终解决方案集中。 但是,在探索其他可能性时,如果您可能碰巧发现存在更多金币的可能性。然后grid[2][3]
也将参与其中。您已将其标记为0
,即该节点的计算将出错。
因此,您需要将原始值恢复到网格项grid[i][j]
。您制作0
的原因是您不想再次包含它,因为您已经访问过。但是对于其他解决方案集,您需要在其中存在原始值。
【讨论】:
“这里你已经改变了grid[i][j]
的值,而改变的值正在影响你的输入”,这不应该是故意的吗?基于规则 (c),我不想再重蹈覆辙。
无论如何你都需要重新审视(探索)它。您是否尝试过为此创建递归树?递归正在帮助您探索所有可能性,而您的 max() 方法正在帮助您选择最大的一个。规则(c)是说您不需要再次选择相同的网格,但根据您的解决方案需要探索周围环境以了解最大值。探索的并不一定意味着它在您的解决方案集中被选中。 @UmedhSinghBundela
我明白你的意思,但是在链接的解决方案中,我们也将其设为负数。 那 不会使其对其他解决方案集不可用吗?
那个链接的解决方案在递归调用之后再次否定了它,这使它回到了原来的值。否定的否定是一样的,对吧? @UmedhSinghBundela
是的,但是在进行递归调用之后完成了恢复。因此,我认为,当递归调用开始执行时,恢复的值对他们来说还不是可用的。你现在明白我困惑的根源了吗?以上是关于为啥在这种情况下只有回溯有效?的主要内容,如果未能解决你的问题,请参考以下文章