简体   繁体   English

这不应该使用回溯算法吗?

[英]Shouldn't this be using a backtracking algorithm?

I am solving some questions on LeetCode. 我在LeetCode上解决了一些问题。 One of the questions is: 其中一个问题是:

Given amxn grid filled with non-negative numbers, find a path from top left to bottom right which minimizes the sum of all numbers along its path.You can only move either down or right at any point in time. 给定amxn网格填充非负数,找到从左上到右下的路径,最小化沿其路径的所有数字的总和。您只能在任何时间点向下或向右移动。

The editorial as well as the solutions posted all use dynamic programming. 社论以及发布的解决方案都使用动态编程。 One of the most upvoted solution is as follows: 最受赞誉的解决方案之一如下:

class Solution {
public:
    int minPathSum(vector<vector<int>>& grid) {
        int m = grid.size();
        int n = grid[0].size(); 
        vector<vector<int> > sum(m, vector<int>(n, grid[0][0]));
        for (int i = 1; i < m; i++)
            sum[i][0] = sum[i - 1][0] + grid[i][0];
        for (int j = 1; j < n; j++)
            sum[0][j] = sum[0][j - 1] + grid[0][j];
        for (int i = 1; i < m; i++)
            for (int j = 1; j < n; j++)
                sum[i][j]  = min(sum[i - 1][j], sum[i][j - 1]) + grid[i][j];
        return sum[m - 1][n - 1];
    }
};

My question is simple: shouldn't this be solved using backtracking? 我的问题很简单:不应该使用回溯来解决这个问题吗? Suppose the input matrix is something like: 假设输入矩阵是这样的:

[ [
[1,2,500] [1,2,500]
[100,500,500] [100500500]
[1,3,4] [1,3,4]
] ]

My doubt is because in DP, the solutions to subproblems are a part of the global solution (optimal substructure). 我怀疑是因为在DP中,子问题的解决方案是全局解决方案(最佳子结构)的一部分。 However, as can be seen above, when we make a local choice of choosing 2 out of (2,100) , we might be wrong, since the future paths might be too expensive (all numbers surrounding 2 are 500 s). 但是,如上所述,当我们选择2 (2,100) 2时,我们可能是错的,因为未来的路径可能太昂贵(2周围的所有数字都是500秒)。 So, how is using dynamic programming justified in this case? 那么,在这种情况下如何使用动态编程?

To summarize: 总结一下:

  1. Shouldn't we use backtracking since we might have to retract our path if we have made an incorrect choice previously (looking at local maxima)? 我们不应该使用回溯,因为如果我们之前做出了错误的选择(查看局部最大值),我们可能不得不收回路径吗?
  2. How is this a dynamic programming question? 这是一个动态编程问题怎么样?

PS: The above solution definitely runs. PS:上面的解决方案肯定会运行。

The example you illustrated above shows that a greedy solution to the problem will not necessarily produce an optimal solution, and you're absolutely right about that. 上面说明的示例表明,对问题的贪婪解决方案不一定能产生最佳解决方案,而且您对此完全正确。

However, the DP solution to this problem doesn't quite use this strategy. 但是,针对此问题的DP解决方案并未完全使用此策略。 The idea behind the DP solution is to compute, for each location, the cost of the shortest path ending at that location. DP解决方案背后的想法是为每个位置计算在该位置结束的最短路径的成本。 In the course of solving the overall problem, the DP algorithm will end up computing the length of some shortest paths that pass through the 2 in your grid, but it won't necessarily use those intermediate shortest paths when determining the overall shortest path to return. 在解决整体问题的过程中,DP算法将最终计算通过网格中2的最短路径的长度,但在确定返回的总体最短路径时,它不一定会使用那些中间最短路径。 Try tracing through the above code on your example - do you see how it computes and then doesn't end up using those other path options? 尝试在示例中跟踪上面的代码 - 您是否看到它如何计算,然后最终不会使用其他路径选项?

Shouldn't we use backtracking since we might have to retract our path if we have made an incorrect choice previously (looking at local maxima)? 我们不应该使用回溯,因为如果我们之前做出了错误的选择(查看局部最大值),我们可能不得不收回路径吗?

In a real-world scenario, there will be quite a few factors that will determine which algorithm will be better suited to solve this problem. 在实际情况中,会有很多因素决定哪种算法更适合解决此问题。

This DP solution is alright in the sense that it will give you the best performance/memory usage when handling worst-case scenarios. 从某种意义上说,这种DP解决方案可以在处理最坏情况时为您提供最佳的性能/内存使用率。

Any backtracking/dijkstra/A* algorithm will need to maintain a full matrix as well as a list of open nodes. 任何回溯/ dijkstra / A *算法都需要维护一个完整的矩阵以及一个开放节点列表。 This DP solution just assumes every node will end up being visited, so it can ditch the open node list and just maintain the costs buffer. 这个DP解决方案只是假设每个节点最终都会被访问,因此它可以放弃开放节点列表并只维护成本缓冲区。

By assuming every node will be visited, it also gets rid of the "which node do I open next" part of the algorithm. 通过假设每个节点都将被访问,它也摆脱了算法的“下一个打开哪个节点”部分。

So if optimal worst-case scenario performance is what we are looking for, then this algorithm is actually going to be very hard to beat. 因此,如果我们正在寻找最佳的最坏情况场景性能,那么这个算法实际上将很难被击败。 But wether that's what we want or not is a different matter. 但是,我们想要与否的是另一回事。

How is this a dynamic programming question? 这是一个动态编程问题怎么样?

This is only a dynamic programming question in the sense that there exists a dynamic programming solution for it. 这只是一个动态编程问题,因为它存在动态编程解决方案。 But by no means is DP the only way to tackle it. 但绝不是DP是解决它的唯一方法。

Edit : Before I get dunked on, yes there are more memory-efficient solutions, but at very high CPU costs in the worst-case scenarios. 编辑 :在我开始扣篮之前,是的,有更多的内存效率解决方案,但在最坏的情况下,CPU成本非常高。

For your input 供您参考

[
[  1,   2, 500]
[100, 500, 500]
[  1,   3,   4]
]

sum array results to sum数组结果

[
[  1,   3,  503]
[101, 503, 1003]
[102, 105,  109]
]

And we can even retrace shortest path: 我们甚至可以追溯最短的路径:

109, 105, 102, 101, 1

Algorithm doesn't check each path, but use the property that it can take previous optimum path to compute current cost: 算法不检查每条路径,但使用可以采用先前最佳路径计算当前成本的属性:

sum[i][j] = min(sum[i - 1][j], // take better path between previous horizontal
                sum[i][j - 1]) // or previous vertical
            + grid[i][j]; // current cost

Backtracking, in itself, doesn't fit this problem particularly well. 回溯本身并不适合这个问题。

Backtracking works well for problems like eight queens, where a proposed solution either works, or it doesn't. 回溯适用于像八个皇后这样的问题,其中提议的解决方案可行或不可行。 We try a possible route to a solution, and if it fails, we backtrack and try another possible route, until we find one that works. 我们尝试了一种可能的解决方案路线,如果失败了,我们会回溯并尝试另一种可能的路线,直到找到一种有效的路线。

In this case, however, every possible route gets us from the beginning to the end. 然而,在这种情况下,每条可能的路线都会从开始到结束。 We can't just try different possibilities until we find one that works. 在找到有效的方法之前,我们不能尝试不同的可能性。 Instead, we have to basically try every route from beginning to end, until one find the one that works the best (the lowest weight, in this case). 相反,我们必须从头到尾基本上尝试每条路线,直到找到效果最好的路线(在这种情况下最低的重量)。

Now, it's certainly true that with backtracking and pruning, we could (perhaps) improve our approach to this solution, to at least some degree. 现在,通过回溯修剪,我们可以(或许)至少在某种程度上改进我们对此解决方案的方法。 In particular, let's assume you did a search that started by looking downward (if possible) and then to the side. 特别是,让我们假设您通过向下看(如果可能)然后向侧面进行搜索。 In this case, with the input you gave its first attempt would end up being the optimal route. 在这种情况下,使用您第一次尝试的输入将最终成为最佳路线。

The question is whether it can recognize that, and prune some branches of the tree without traversing them entirely. 问题是它是否能够识别并修剪树的某些分支而不完全遍历它们。 The answer is that yes, it can. 答案是肯定的,它可以。 To do that, it keeps track of the best route it's found so far , and based upon that, it can reject entire sub-trees. 为此,它会跟踪到目前为止找到的最佳路线,并且基于此,它可以拒绝整个子树。 In this case its first route gives a total weight of 109. Then it tries to the right of the first node, which is a 2, for a total weight of 3 so far. 在这种情况下,它的第一条路线总重量为109.然后它尝试到第一个节点的右边,这是一个2,到目前为止总重量为3。 That's smaller than 109, so it proceeds. 那比小于109,所以它继续下去。 From there, it looks downward and gets to the 500. That gives a weight of 503, so without doing any further looking, it knows no route from there can be suitable, so it stops and prunes off all the branches that start from that 500. Then it tries rightward from the 2 and finds another 500. This lets it prune that entire branch as well. 从那里,它向下看,并达到500.这给了503的重量,所以没有做任何进一步的观察,它知道没有合适的路线,所以它停止并修剪从500开始的所有分支然后它从2向右尝试并找到另一个500.这使得它也可以修剪整个分支。 So, in these cases, it never looks at the third 500, or the 3 and 4 at all--just by looking at the 500 nodes, we can determine that those can't possibly yield an optimal solution. 因此,在这些情况下,它永远不会看到第三个500,或者根本看不到第三个和第四个 - 仅仅通过查看500节点,我们可以确定那些不可能产生最佳解决方案。

Whether that's really an improvement on the DP strategy largely comes down to a question of what operations cost how much. 这是否真的是DP策略的改进很大程度上归结为一个运营成本是多少的问题。 For the task at hand, it probably doesn't make much difference either way. 对于手头的任务,它可能没有太大的区别。 If, however, your input matrix was a lot larger, it might. 但是,如果您的输入矩阵更大,则可能。 For example, we might have a large input stored in tiles. 例如,我们可能会在tile中存储大量输入。 With a DP solution, we evaluate all the possibilities, so we always load all the tiles. 通过DP解决方案,我们可以评估所有可能性,因此我们始终会加载所有磁贴。 With a tree-trimming approach, we might be able to completely avoid loading some tiles at all, because the routes including those tiles have already been eliminated. 使用树修剪方法,我们可以完全避免加载一些瓷砖,因为包括那些瓷砖的路线已经被消除。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM