为什么只有回溯在这种情况下有效?
Why does only backtracking work in this scenario?
我正在 LeetCode.com 上解决这个问题:
In a gold mine grid of size m * n, each cell in this mine has an integer representing the amount of gold in that cell, 0 if it is empty. Return the maximum amount of gold you can collect under the conditions:-
(a) Every time you are located in a cell you will collect all the gold in that cell;
(b) From your position you can walk one step to the left, right, up or down.
(c) You can't visit the same cell more than once;
(d) Never visit a cell with 0 gold.
(e) You can start and stop collecting gold from any position in the grid that has some gold.
For the grid: [[0,6,0],[5,8,7],[0,9,0]]
the output is: 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
,因为他们将修改后的网格恢复为原始形式)。
我知道他们在回溯,而我没有。但是我无法理解我做错了什么,因为从逻辑上讲,我在做完全相同的事情。我使用调试语句来跟踪问题,但是由于有很多递归调用,因此很难跟进。
有人可以指出我上面的代码中的逻辑谬误是什么吗?
谢谢!
您所有的递归调用都可能会修改网格,并且不会恢复它。
这意味着第一次访问一个单元格将阻止所有其他后续尝试。
例如,如果首先计算 dig(grid, i+1, j)
,则当您执行其他三个方向时,计算期间访问的单元格中有 none 可用。
如果您的代码恰好从左上角开始,在您的示例中先向下,您将访问 1-2-3-4-3 并卡住。
走完这条路,左上角就没有路了,虽然一开始有很多,而且有的比13多了很多。
(您可能认为您将从任一方向找到一条路径,但这取决于评估顺序。)
共享可变状态和递归是一个非常棘手的组合。
您的代码中有一点似乎有误:
//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...
如果您在问题中使用那里的解决方案,您还可以节省在递归堆栈中创建内存的时间。
简单回答一下为什么只有回溯才能解决这个问题:
您需要了解您的解决方案,它看起来像:
- 查看每个网格项是否可以作为解决方案集的一部分。
- 为此,您 select 一个网格项目(项目值应大于 0)
- 然后你从那个项目探索周围的所有环境。并使用 DFS 继续探索他们的周围环境,等等,直到满足退出条件。
- 现在,在您的解决方案中,您已经改变了 grid[i][j] 的值(为了便于理解,假设 grid[2][3] 发生了改变)当且仅当项目 selected 存在于最终解决方案集中。
- 但是,在探索其他可能性的同时,如果您碰巧发现了一些存在更多金币的可能性。然后还有
grid[2][3]
会涉及到。您已将其标记为 0
,即该节点的计算将出错。
因此,您需要恢复网格项的原始值grid[i][j]
。你这样做的原因 0
是你不想再次包含它,因为你已经访问过。但是对于其他解决方案集,您需要原始值存在于那里。
我正在 LeetCode.com 上解决这个问题:
In a gold mine grid of size m * n, each cell in this mine has an integer representing the amount of gold in that cell, 0 if it is empty. Return the maximum amount of gold you can collect under the conditions:-
(a) Every time you are located in a cell you will collect all the gold in that cell;
(b) From your position you can walk one step to the left, right, up or down.
(c) You can't visit the same cell more than once;
(d) Never visit a cell with 0 gold.
(e) You can start and stop collecting gold from any position in the grid that has some gold.
For the grid:[[0,6,0],[5,8,7],[0,9,0]]
the output is: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
,因为他们将修改后的网格恢复为原始形式)。
我知道他们在回溯,而我没有。但是我无法理解我做错了什么,因为从逻辑上讲,我在做完全相同的事情。我使用调试语句来跟踪问题,但是由于有很多递归调用,因此很难跟进。
有人可以指出我上面的代码中的逻辑谬误是什么吗?
谢谢!
您所有的递归调用都可能会修改网格,并且不会恢复它。
这意味着第一次访问一个单元格将阻止所有其他后续尝试。
例如,如果首先计算 dig(grid, i+1, j)
,则当您执行其他三个方向时,计算期间访问的单元格中有 none 可用。
如果您的代码恰好从左上角开始,在您的示例中先向下,您将访问 1-2-3-4-3 并卡住。
走完这条路,左上角就没有路了,虽然一开始有很多,而且有的比13多了很多。
(您可能认为您将从任一方向找到一条路径,但这取决于评估顺序。)
共享可变状态和递归是一个非常棘手的组合。
您的代码中有一点似乎有误:
//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...
如果您在问题中使用那里的解决方案,您还可以节省在递归堆栈中创建内存的时间。
简单回答一下为什么只有回溯才能解决这个问题:
您需要了解您的解决方案,它看起来像:
- 查看每个网格项是否可以作为解决方案集的一部分。
- 为此,您 select 一个网格项目(项目值应大于 0)
- 然后你从那个项目探索周围的所有环境。并使用 DFS 继续探索他们的周围环境,等等,直到满足退出条件。
- 现在,在您的解决方案中,您已经改变了 grid[i][j] 的值(为了便于理解,假设 grid[2][3] 发生了改变)当且仅当项目 selected 存在于最终解决方案集中。
- 但是,在探索其他可能性的同时,如果您碰巧发现了一些存在更多金币的可能性。然后还有
grid[2][3]
会涉及到。您已将其标记为0
,即该节点的计算将出错。
因此,您需要恢复网格项的原始值grid[i][j]
。你这样做的原因 0
是你不想再次包含它,因为你已经访问过。但是对于其他解决方案集,您需要原始值存在于那里。