假设我在Java中使用m x n
矩阵。
我想找到从第一列到最后一列的最大遍历成本。每个值代表产生的成本。我可以在矩阵上向上,向下和向右行进。每个单元只能访问一次。允许从列的顶部单元格转换到相同的底部,反之亦然。
为简单起见,请考虑以下矩阵:
2 3 17
4 1 -1
5 0 14
如果我想找到最高费用,我的回答是46(2→5→4→1→3→0→14→17)。
我尝试使用动态方法使用以下递归关系解决此问题:
maxCost(of destination node) = max{ maxCost(at neighbouring node 1), maxCost(at neighbouring node 2), maxCost(at neighbouring node 3) } + cost(of destination node)
在这种情况下,它将类似于:
maxCost(17) = max{ maxCost(3), maxCost(-1), maxCost(14) } + 17;
因为,每个单元格只允许访问一次,我知道我需要维护一个相应的m x n
isVisited
矩阵。但是,我无法弄清楚如何维护isVisited
矩阵。当计算maxCost(3)时,将修改矩阵;但是对于maxCost(-1)和maxCost(14),我会要求它的初始状态(会丢失)。
我的方法是否适用于此问题?另外,我无法弄清楚我的功能应该如何。 (这是我第一次尝试动态编程)。
答案 0 :(得分:5)
这是一个很好的,有点棘手的问题。对于DP解决方案,我们必须以符合principle of optimality的方式对其进行说明。
这要求我们定义一个“状态”,以便可以根据一个n路决策来编写问题,这个决策将我们带到一个新的状态,而这个状态反过来又是同一个问题的一个新的,更小的版本。
状态的合适选择是遍历的当前位置加上有符号整数f,表示当前列中的未遍历(我称之为“空闲”)行的位置和数量。我们可以把它写成三元组[i,j,f]。
f的值告诉我们是否可以向上和/或向下移动。 (除非我们在正确的列中,否则总是可以向右移动,并且永远不可能向左移动。)如果f为负,则在当前位置“上方”有f个自由行,这可能包围到矩阵底部。如果是肯定的,则下面有f
个免费行。请注意,f = m-1和f = 1-m意味着相同的事情:除了当前位置之外,所有行都是空闲的。为简单起见,我们将使用f == m-1来表示该情况。
单个整数f是我们描述自由空间所需要的全部因为我们只能以1的步长遍历,而我们永远不会向左移动。因此,同一列中不能有不连续的自由空间组。
现在DP“决定”是一个四方选择:
设,C(t)是DP中的最大代价函数,其中t是三元组[i,j,f]。然后我们可以实现的最大成本是在做出上面的最佳决策1到4之后,来自矩阵的成本A [i,j]加到其余遍历的成本上。最佳决策只是产生最高成本的决策!
所有这些使得C成为所有元素都是有条件的集合的最大值。
C[i,j,f] = max { A[i,j] if j==n-1, // the "stand pat" case
{ A[i,j]+C[i,j+1,m-1] if j<n-1 // move right
{ A[i,j]+C[i+1,j,f-1] if f>0 // move down
{ A[i,j]+C[i-1,j,2-m] if f==m-1 // first move in col is up
{ A[i,j]+C[i-1,j,f+1] if f<0 // other moves up
有时单词比代数更清晰。 “失败”案件将是......
从位置[i,j]到目标(右列)的一个潜在最大路径成本是矩阵值A [i,j]加上通过向下移动到位置[i + 1,j]可获得的最大成本。但是只有在那里有空闲空间(f> 0)时我们才能向下移动。向下移动后,其中少了一个(f-1)。
这解释了递归表达式为C [i + 1,j,f-1]的原因。其他案例只是其中的变种。
另请注意,上面隐含了“基本案例”。在f = 0和j = n-1的所有状态中,你都拥有它们。递归必须停止。
要获得最终答案,您必须考虑所有有效起始位置的最大值,这是第一列元素,并且列中的所有其他元素都是空的:max C[i,0,m-1]
表示i = 0..m- 1。
由于您未能成功找到DP,因此这是一个表格构建代码,以显示它的工作原理。 DP中的依赖关系需要谨慎选择评估顺序。当然f
参数可以是负数,并且行参数包装。我在调整f和i的2个函数中处理了这些。储存量为O(平方公尺):
import java.util.Arrays;
public class MaxPath {
public static void main(String[] args) {
int[][] a = {
{2, 3, 17},
{4, 1, -1},
{5, 0, 14}
};
System.out.println(new Dp(a).cost());
}
}
class Dp {
final int[][] a, c;
final int m, n;
Dp(int[][] a) {
this.a = a;
this.m = a.length;
this.n = a[0].length;
this.c = new int[2 * m - 2][m];
}
int cost() {
Arrays.fill(c[fx(m - 1)], 0);
for (int j = n - 1; j >= 0; j--) {
// f = 0
for (int i = 0; i < m; i++) {
c[fx(0)][i] = a[i][j] + c[fx(m - 1)][i];
}
for (int f = 1; f < m - 1; f++) {
for (int i = 0; i < m; i++) {
c[fx(-f)][i] = max(c[fx(0)][i], a[i][j] + c[fx(1 - f)][ix(i - 1)]);
c[fx(+f)][i] = max(c[fx(0)][i], a[i][j] + c[fx(f - 1)][ix(i + 1)]);
}
}
// f = m-1
for (int i = 0; i < m; i++) {
c[fx(m - 1)][i] = max(c[fx(0)][i],
a[i][j] + c[fx(m - 2)][ix(i + 1)],
a[i][j] + c[fx(2 - m)][ix(i - 1)]);
}
System.out.println("j=" + j + ": " + Arrays.deepToString(c));
}
return max(c[fx(m - 1)]);
}
// Functions to account for negative f and wrapping of i indices of c.
int ix(int i) { return (i + m) % m; }
int fx(int f) { return f + m - 2; }
static int max(int ... x) { return Arrays.stream(x).max().getAsInt(); }
}
这是输出。如果您了解DP,则可以看到它构建从列j = 2到j = 0的最佳路径。矩阵由f = -1,0,1,2和i = 0,1,2索引。
j=2: [[31, 16, 14], [17, -1, 14], [17, 13, 31], [31, 30, 31]]
j=1: [[34, 35, 31], [34, 31, 31], [34, 32, 34], [35, 35, 35]]
j=0: [[42, 41, 44], [37, 39, 40], [41, 44, 42], [46, 46, 46]]
46
结果显示(j = 0,列f = m-1 = 2)所有元素,如果第一列与起点一样好。
答案 1 :(得分:4)
这是一个艰难的。请注意,由于您的路径无法重复访问过的单元格,因此您可能的路径将具有类似蛇的行为,例如:
我们的想法是在f[j][i]
中存储以单元格(j, i)
结尾的最大路径长度。让我们说现在我们想要从f[j][i-1]
过渡到f[j'][i]
。然后,我们可以选择直接从单元格(j, i)
转到单元格(j', i)
,也可以通过环绕顶部/边框边缘从单元格(j, i)
转到单元格(j', i)
。因此,f[j][i]
的更新可以计算为:
,其中
这里a
是给定的数组。
现在的问题是如何有效地计算sum(a[j..j'][i]
,否则运行时将是O(m^3n)
。您可以通过为tmp_sum
使用临时变量sum(a[j..j'][i])
来解决此问题,并在递增j
时递增。{1}}。然后算法的runitme将是O(m^2 n)
。
以下是一个示例实现:
package stackoverflow;
public class Solver {
int m, n;
int[][] a, f;
public Solver(int[][] a) {
this.m = a.length;
this.n = a[0].length;
this.a = a;
}
void solve(int row) {
f = new int[m][n];
for (int i = 0; i < m; ++i)
for (int j = 0; j < n; ++j)
f[i][j] = Integer.MIN_VALUE;
for (int i = 0; i < n; ++i) {
int sum = 0;
for (int j = 0; j < m; ++j)
sum += a[j][i];
for (int j1 = 0; j1 < m; ++j1) {
int tmp_sum = 0;
boolean first = true;
for (int j2 = j1; j2 != j1 || first; j2 = (j2+1)%m) {
if (first)
first = false;
tmp_sum += a[j2][i];
int best_sum = Math.max(tmp_sum, sum - tmp_sum +a[j1][i]+a[j2][i]);
if (j1 == j2)
best_sum = a[j1][i];
int prev = 0;
if (i > 0)
prev = f[j1][i-1];
f[j2][i] = Math.max(f[j2][i], best_sum + prev);
}
}
}
System.out.println(f[row][n-1]);
}
public static void main(String[] args) {
new Solver(new int[][]{{2, 3, 17}, {4, 1, -1}, {5, 0, 14}}).solve(0); //46
new Solver(new int[][]{{1, 1}, {-1, -1}}).solve(0); //2
}
}
答案 2 :(得分:2)
感谢大家的贡献。
我使用recursive
使用system stack
技术提出了一个解决方案。我认为我的解决方案相对容易理解。
这是我的代码:
import java.util.Scanner;
public class MatrixTraversal {
static int[][] cost;
static int m, n, maxCost = 0;
public static void solve(int currRow, int currCol, int[][] isVisited, int currCost) {
int upperRow, lowerRow, rightCol;
isVisited[currRow][currCol] = 1;
currCost += cost[currRow][currCol]; //total cost upto current position
if( currCol == (n - 1) //if we have reached the last column in matrix
&& maxCost < currCost ) //and present cost is greater than previous maximum cost
maxCost = currCost;
upperRow = ((currRow - 1) + m) % m; //upper row value taking care of teleportation
lowerRow = (currRow + 1) % m; //lower row value taking care of teleportation
rightCol = currCol + 1; //right column value
if( isVisited[upperRow][currCol] == 0 ) //if upper cell has not been visited
solve(upperRow, currCol, isVisited, currCost);
if( isVisited[lowerRow][currCol] == 0 ) //if lower cell has not been visited
solve(lowerRow, currCol, isVisited, currCost);
if( rightCol != n && //if we are not at the last column of the matrix
isVisited[currRow][rightCol] == 0 ) //and the right cell has not been visited
solve(currRow, rightCol, isVisited, currCost);
isVisited[currRow][currCol] = 0;
}
public static void main(String[] args) {
int[][] isVisited;
int i, j;
Scanner sc = new Scanner(System.in);
System.out.print("Enter the no.of rows(m): ");
m = sc.nextInt();
System.out.print("Enter the no.of columns(n): ");
n = sc.nextInt();
cost = new int[m][n];
isVisited = new int[m][n];
System.out.println("Enter the cost matrix:");
for(i = 0; i < m; i++)
for(j = 0; j < n; j++)
cost[i][j] = sc.nextInt(); //generating the cost matrix
for(i = 0; i < m; i++)
solve(i, 0, isVisited, 0); //finding maximum traversal cost starting from each cell in 1st column
System.out.println(maxCost);
}
}
但是,我不确定这是否是计算解决方案的最佳和最快方式。
请告诉我您的看法。我会相应地接受这个答案。
答案 3 :(得分:1)
一种可能的优化是,我们只需要为具有负数的列或长度小于m
的非负列的序列计算不同的选项(除了完整的总和),由具有负数的列包围。我们需要一列和一个(概念)矩阵来计算这类列的序列的最大值;当前列的矩阵,可转换为每个出口点的最大列数。每个矩阵表示y
处的输入和y'
处的退出的最大总和以及恰好位于入口点之前的先前最大值(每个都有两种可能性,具体取决于路径方向)。矩阵沿对角线(意为sum entry...exit = sum exit...entry
)对称反射,直到添加每个入口点的各个先前最大值。
在示例中添加一个带负数的附加列,我们可以看到如何应用累积总和:
2 3 17 -3
4 1 -1 15
5 0 14 -2
(我们暂时忽略前两个非负列,稍后再添加15个。)
Third column:
y' 0 1 2
y
0 17 30 31
1 30 -1 30
2 31 30 14
对于第四列矩阵,每个入口点需要与前一列中相同出口点的最大值组合。例如,输入点0
添加了max(17,30,31)
:
y' 0 1 2
y
0 -3 12 10 + max(17,30,31)
1 12 15 13 + max(30,-1,30)
2 10 13 -2 + max(31,30,14)
=
28 43 41
42 45 43
41 44 29
我们可以看到最终的最大值有(进入,退出)(1,1)
和解决方案:
15 + (0,1) or (2,1) + (1,1)
答案 4 :(得分:1)
让我们看看这里的动态编程答案与答案中的蛮力方法有什么不同,以及我们如何调整你的答案。举个简单的例子,
a = {{17, -3}
,{-1, 15}}
蛮力将遍历并比较所有路径:
17,-3
17,-3,15
17,-1,15
17,-1,15,-3
-1,15
-1,15,-3
-1,17,-3
-1,17,-3,15
动态编程解决方案利用了列之间的选择点,因为只有一种可能性 - 向右移动。在列之间的每次移动中,动态编程解决方案使用max
函数应用修剪方法,该方法将搜索限制为已证实的成本高于其他路径的路径。
Gene提供的递归解决方案中的上下选择导致在svs解决方案的循环中发现类似的遍历,这意味着将修剪同一列中的进入和退出之间的选择。再看看我们的例子:
a = {{17, -3}
,{-1, 15}}
f(-1) -> max(15,15 - 3)
-> 17 -> max(-3,-3 + 15)
f(17) -> max(-3,-3 + 15)
-> -1 -> max(15,15 - 3)
无需检查完整路径总和-1,15,-3
或同时检查17 - 1 + 15
和17 - 1 + 15 - 3
,因为在每种情况下我们都知道哪个结尾会更大,这要归功于{{ 1}}功能:max
。
矩阵阵列解决方案与递归略有不同,但效果相似。我们只关注列{,17 - 1 + 15
之间的移动,这只能在一个地方发生,我们选择仅将添加到目前为止最好的总和为j to j + 1
计算j
。看一下这个例子:
j + 1
在a = {{17, -3}
,{-1, 15}}
时间内计算j = 0
列出口点的最佳总和矩阵:
O(m^2)
现在,对于17
16
,我们只计算沿着列j = 1
的列j = 1
可实现的最佳路径,并且在列j = 1
上有退出点,记住要将这些路径的入口点添加到以前的最佳位置(意思是从列到左边的数字,用*表示:
best exit at -3 = max(-3 + 17*, 15 - 3 + 16*) = 28
best exit at 15 = max(15 + 16*, -3 + 15 + 17*) = 31
现在要调整你的版本,考虑如何改变它,以便递归在每一步中选择从后续调用中返回的最大总和。