【问题标题】:Find maximum length of good path in a grid在网格中找到好的路径的最大长度
【发布时间】:2015-04-08 11:03:05
【问题描述】:

给定一个 N*N 网格。现在我们需要找到一个最大长度的好路径,其中好的路径定义如下:

  1. 好的路径总是从标记为 0 的单元格开始
  2. 我们只能向左、向右、向上或向下移动
  3. 如果第 i 个单元格的值为 A,则路径中下一个单元格的值必须为 A+1。

现在考虑到这几个条件,我需要找出可以制作的最大路径的长度。我还需要计算最大长度的路径。

示例:设 N=3,我们有 3*3 矩阵如下:

0 3 2               
3 0 1               
2 1 0            

那么这里最大的好路径长度是3,这样好的路径的数量是4。

0 3 2
3 0 1
2 1 0

0 3 2
3 0 1
2 1 0

0 3 2
3 0 1
2 1 0

0 3 2
3 0 1
2 1 0

【问题讨论】:

  • 对内存使用或CPU效率有什么要求吗?
  • @AndreiNikolaenko 是的,时间复杂度和空间复杂度必须是可接受和可计算的

标签: c++ algorithm recursion dynamic-programming


【解决方案1】:

这个问题是Longest Path Problem 的变体,但是您的限制使这个问题变得容易得多,因为图表实际上是Directed Acyclic Graph (DAG),因此问题可以有效地解决。

定义有向图G=(V,E)如下:

  • V = { all cells in the matrix}(完整性检查:|V| = N^2)
  • E = { (u,v) | u is adjacent to v AND value(u) + 1 = value(v) }

请注意,上述定义的结果图是一个 DAG,因为你不能有任何循环,因为它会导致有一些边 e= (u,v) 使得 value(u) > value(v)

现在,您只需要从任意起点找到longest path in a DAG。这是由图上的topological sort 完成的,然后使用动态规划:

init:
for every source in the DAG:
D(v) = 0            if value(v) = 0
       -infinity    otherwise
step:
for each node v from first to last (according to topological sort)
   D(v) = max{D(u) + 1 | for each edge (u,v) }

完成后,找到具有最大值D(v)的节点v,这是最长的“好路径”的长度。
通过重新滚动上述内容来查找路径本身,从最大 D(v) 向后追溯步骤,直到返回值为 0 的初始节点。

这种方法的复杂性是O(V+E) = O(n^2)


由于您正在寻找最长路径的数量,您可以稍微修改此解决方案以计算到达每个节点的路径数量,如下所示:

Topological sort the nodes, let the sorted array be arr (1)
For each node v from start to end of arr:
   if value(v) = 0:
        set D(v) = 1 
   else
        sum = 0
        for each u such that (u,v) is an edge: (2)
            sum = sum + D(u) 
        D(v) = sum

上面将为您找到每个节点v 到达它的“好路径”D(v) 的数量。您现在要做的就是找到具有和节点v 的最大值x 使得value(v) = xD(v) > 0,并将到达具有value(v) 的任何节点的路径数求和:

max = 0
numPaths = 0
for each node v:
    if value(v) == max:
         numPaths = numPaths + D(v)
    else if value(v) > max AND D(v) > 0:
         numPaths = D(v)
         max = value(v)
return numPaths

注意事项: (1) - “常规”排序在这里有效,但需要 O(n^2logn) 时间,而拓扑排序需要 O(n^2) 时间
(2) 提醒一下,(u,v) 是一条边,如果:(1) uv 相邻 (2) value(u) + 1 = value(v)

【讨论】:

  • 我不需要找到路径我只需要最大长度和这样长度路径的计数
  • @user3840069 D(v)的最大值是最长路径的长度
  • 是的,这一点我做得很好。但是这个长度会有多少条路径呢?
  • @user3840069 您可以在反向图(反向节点方向)上执行 BFS,而不是查找单个路线,而是维护一个计数器并将其乘以您每一步的选择数。结果计数将是到达每个最大 D(v) 节点的选择数。对每个这样的最大节点重复和总结,你应该得到最长路径的计数
  • 你能提供你的 C++ 或 java 实现吗?
【解决方案2】:

您可以通过简单的广度优先搜索来做到这一点。

首先找到所有标记为 0 的单元格。(这是 O(N2)。)在每个这样的单元格上放置一个步行器。每个 walker 都有一个数字“p”,初始化为 1。

现在迭代:

所有步行者都站在具有相同数字 k 的单元格上。每个 walker 都会寻找标记为 k+1 的相邻单元格(左、右、上或下)。

如果没有步行者看到这样的单元格,搜索结束。最长路径的长度为k,路径的数量为所有步行者的p之和。

如果某些步行者看到这样的数字,请杀死任何没有看到的步行者。

每个步行者移动到一个好的相邻单元格。如果一个步行者看到不止一个好细胞,它就会分成与好细胞一样多的步行者,每个人进入。 (每个“子”具有与其“父”相同的p 值。)如果两个或多个步行者在同一个单元格中相遇(即,如果多条路径通向该单元格),那么它们将组合成一个步行者,其'p' 值是它们的 'p' 值之和。

这个算法是O(N2),因为没有一个cell可以被多次访问,并且walker的数量不能超过cell的数量。

【讨论】:

  • C++/Java 代码将更好地解释您的方法。或者伪代码也可以达到目的。我不明白你想说什么。我们还需要计算此类路径的数量
  • @user3840069:我们确实计算了此类路径的数量。您熟悉广度优先搜索的概念吗?
  • 是的,我知道 BFS 是如何工作的。但只是为了更好地理解代码会很好地解释事情
  • 如果你想了解这个解决方案——它实际上是一个非常简单的 BFS——那么你必须付出一点努力。如果您希望有人为您编写代码,elance 之类的网站可以提供帮助。
【解决方案3】:

我是用 ActionScript 做的,希望它是可读的。我认为它工作正常,但我可能错过了一些东西。

const N:int = 9; // field size
const MIN_VALUE:int = 0; // start value

var field:Array = [];

// create field - not relevant to the task
var probabilities:Array = [0,1,2,3,4,5];
for (var i:int = 0; i < N * N; i++) field.push(probabilities[int(Math.random() * probabilities.length)]);//RANGE));
print_field();

// initial chain fill. We will find any chains of adjacent 0-1 elements.
var chain_list:Array = [];
for (var offset:int = 0; offset < N * N - 1; offset++) {
    if (offset < N * N - N) { // y coordinate is not the lowest
        var chain:Array = find_chain(offset, offset + N, MIN_VALUE);
        if (chain) chain_list.push(chain);
    }
    if ((offset % N) < N - 1) { // x coordinate is not the rightmost
        chain = find_chain(offset, offset + 1, MIN_VALUE);
        if (chain) chain_list.push(chain);
    }
}

var merged_chain_list:Array = chain_list;
var current_value:int = MIN_VALUE + 1;

// for each found chain, scan its higher end for more attached chains
// and merge them into new chain if found
while(chain_list.length) {
    chain_list = [];
    for (i = 0; i < merged_chain_list.length; i++) {
        chain = merged_chain_list[i];
        offset = chain[chain.length - 1];

        if (offset < N * N - N) {
            var tmp:Array = find_chain(offset, offset + N, current_value);
            if (tmp) chain_list.push(merge_chains(chain, tmp));
        }
        if (offset > N) {
            tmp = find_chain(offset, offset - N, current_value);
            if (tmp) chain_list.push(merge_chains(chain, tmp));
        }
        if ((offset % N) < N - 1) {
            tmp = find_chain(offset, offset + 1, current_value);
            if (tmp) chain_list.push(merge_chains(chain, tmp));
        }
        if (offset % N) {
            tmp = find_chain(offset, offset - 1, current_value);
            if (tmp) chain_list.push(merge_chains(chain, tmp));
        }
    }

    //save the last merged result if any and try the next value
    if (chain_list.length) {
        merged_chain_list = chain_list;
        current_value++;
    }
}

// final merged list is a list of chains of a same maximum length
print_chains(merged_chain_list);

function find_chain(offset1, offset2, current_value):Array {
    // returns always sorted sorted from min to max
    var v1:int = field[offset1];
    var v2:int = field[offset2];
    if (v1 == current_value && v2 == current_value + 1) return [offset1, offset2];
    if (v2 == current_value && v1 == current_value + 1) return [offset2, offset1];
    return null;
}

function merge_chains(chain1:Array, chain2:Array):Array {
    var tmp:Array = [];
    for (var i:int = 0; i < chain1.length; i++) tmp.push(chain1[i]);
    tmp.push(chain2[1]);
    return tmp;
}

function print_field():void {
    for (var pos_y:int = 0; pos_y < N; pos_y++) {
        var offset:int = pos_y * N;
        var s:String = "";
        for (var pos_x:int = 0; pos_x < N; pos_x++) {
            var v:int = field[offset++];
            if (v == 0) s += "[0]"; else s += " " + v + " ";
        }
        trace(s);
    }
}

function print_chains(chain_list):void {
    var cl:int = chain_list.length;
    trace("\nchains found: " + cl);
    if (cl) trace("chain length: " + chain_list[0].length);
    for (var i:int = 0; i < cl; i++) {
        var chain:Array = chain_list[i];
        var s:String = "";
        for (var j:int = 0; j < chain.length; j++) s += chain[j] + ":" + field[chain[j]] + " ";
        trace(s);
    }
}

样本输出:

 1  2  1  3  2  2  3  2  4 
 4  3  1  2  2  2 [0][0] 1 
[0][0] 1  2  4 [0] 3  3  1 
[0][0] 5  4  1  1 [0][0] 1 
 2  2  3  4  3  2 [0] 1  5 
 4 [0] 3 [0] 3  1  4  3  1 
 1  2  2  3  5  3  3  3  2 
 3  4  2  1  2  4  4  4  5 
 4  2  1  2  2  3  4  5 [0]

chains found: 2
chain length: 5
23:0 32:1 41:2 40:3 39:4 
33:0 32:1 41:2 40:3 39:4 

【讨论】:

  • 你能提供 C++ 或 java 的实现吗,因为我对动作脚本一无所知
【解决方案4】:

我用我自己的 Lisp 方言实现了它,所以源代码对你没有太大帮助 :-) ...

编辑:也添加了 Python 版本。

不管怎样,这个想法是:

  • 编写一个函数paths(i, j) --&gt; (maxlen, number),它返回从(i, j) 开始的最大路径长度以及存在的路径数量..
  • 此函数是递归的,查看 (i, j) 的值为 M[i][j]+1 的邻居将调用 paths(ni, nj) 以获取有效邻居的结果
  • 如果邻居的最大长度大于当前最大长度,则设置新的当前最大长度并重置计数器
  • 如果最大长度与当前长度相同,则将计数器添加到总长度中
  • 如果最大长度较小,则忽略该邻居结果
  • 缓存单元的计算结果(这非常重要!)。在我的版本中,代码分为两个相互递归的函数:paths首先检查缓存,否则调用compute-pathscompute-paths 在处理邻居时调用 paths。递归调用的缓存大致相当于显式动态编程方法,但有时更容易实现。

要计算最终结果,您基本上执行相同的计算,但将所有0 单元格的结果相加,而不是考虑邻居。

请注意,不同路径的数量可能会变得很大,这就是为什么枚举所有路径不是一个可行的选择,并且缓存/DP 是必须的:例如,对于值为 M[i][j] = i+jN=20 矩阵,有 35,345,263,800最大路径长度为 38。

这个算法在时间上是 O(N^2) (每个单元最多被访问一次)并且需要 O(N^2) 空间用于缓存和递归。当然,鉴于输入由 N^2 个数字本身组成,并且您至少需要阅读它们来计算答案,因此您不能期望得到比这更好的结果。

(defun good-paths (matrix)
  (let** ((N (length matrix))
          (cache (make-array (list N N)))
          (#'compute-paths (i j)
            (let ((res (list 0 1))
                  (count (1+ (aref matrix i j))))
              (dolist ((ii jj) (list (list (1+ i) j) (list (1- i) j)
                                     (list i (1+ j)) (list i (1- j))))
                (when (and (< -1 ii N) (< -1 jj N)
                           (= (aref matrix ii jj) count))
                  (let (((maxlen num) (paths ii jj)))
                    (incf maxlen)
                    (cond
                      ((< (first res) maxlen)
                       (setf res (list maxlen num)))
                      ((= (first res) maxlen)
                       (incf (second res) num))))))
              res))
          (#'paths (i j)
            (first (or (aref cache i j)
                       (setf (aref cache i j)
                             (list (compute-paths i j))))))
          (res (list 0 0)))
    (dotimes (i N)
      (dotimes (j N)
        (when (= (aref matrix i j) 0)
          (let (((maxlen num) (paths i j)))
            (cond
              ((< (first res) maxlen)
               (setf res (list maxlen num)))
              ((= (first res) maxlen)
               (incf (second res) num)))))))
    res))

编辑

以下是上述内容在 Python 中的音译,如果你之前没见过 Lisp,应该会更容易理解……

def good_paths(matrix):
    N = len(matrix)
    cache = [[None]*N for i in xrange(N)] # an NxN matrix of None

    def compute_paths(i, j):
        maxlen, num = 0, 1
        count = 1 + matrix[i][j]
        for (ii, jj) in ((i+1, j), (i-1, j), (i, j-1), (i, j+1)):
            if 0 <= ii < N and 0 <= jj < N and matrix[ii][jj] == count:
                nh_maxlen, nh_num = paths(ii, jj)
                nh_maxlen += 1
                if maxlen < nh_maxlen:
                    maxlen = nh_maxlen
                    num = nh_num
                elif maxlen == nh_maxlen:
                    num += nh_num
        return maxlen, num

    def paths(i, j):
        res = cache[i][j]
        if res is None:
            res = cache[i][j] = compute_paths(i, j)
        return res

    maxlen, num = 0, 0
    for i in xrange(N):
        for j in xrange(N):
            if matrix[i][j] == 0:
                c_maxlen, c_num = paths(i, j)
                if maxlen < c_maxlen:
                    maxlen = c_maxlen
                    num = c_num
                elif maxlen == c_maxlen:
                    num += c_num
    return maxlen, num

【讨论】:

  • 你能用 C++ 或 java 提供你的实现吗?因为我不懂 Lisp
  • @user3840069:我添加了一个 Python 版本,如果您了解 C++ 或 Java,应该很容易理解...
猜你喜欢
  • 2018-12-31
  • 2014-04-16
  • 1970-01-01
  • 2021-09-21
  • 2020-01-11
  • 2015-06-01
  • 2012-05-20
  • 1970-01-01
相关资源
最近更新 更多