编程之道:面试和算法心得第二部分 算法心得第五章 动态规划5.3 格子取数

5.3 格子取数

题目描述

有n*n个格子,每个格子里有正数或者0,从最左上角往最右下角走,只能向下和向右,一共走两次(即从左上角走到右下角走两趟),把所有经过的格子的数加起来,求最大值SUM,且两次如果经过同一个格子,则最后总和SUM中该格子的计数只加一次。

分析与解法

初看到此题,因为要让两次走下来的路径总和最大,读者可能最初想到的思路可能是让每一次的路径都是最优的,即不顾全局,只看局部,让第一次和第二次的路径都是最优。

但问题马上就来了,虽然这一算法保证了连续的两次走法都是最优的,但却不能保证总体最优,相应的反例也不难给出,请看下图:

上图中,图一是原始图,那么我们有以下两种走法可供我们选择:

  • 如果按照上面的局部贪优走法,那么第一次势必会如图二那样走,导致的结果是第二次要么取到2,要么取到3,
  • 但若不按照上面的局部贪优走法,那么第一次可以如图三那样走,从而第二次走的时候能取到2 4 4,很显然,这种走法求得的最终SUM值更大;

为了便于读者理解,我把上面的走法在图二中标记出来,而把应该正确的走法在上图三中标示出来,如下图所示:

也就是说,上面图二中的走法太追求每一次最优,所以第一次最优,导致第二次将是很差;而图三第一次虽然不是最优,但保证了第二次不差,所以图三的结果优于图二。由此可知不要只顾局部而贪图一时最优,而丧失了全局最优。

局部贪优不行,我们可以考虑穷举,但最终将导致复杂度过高,所以咱们得另寻良策。

为了方便讨论,我们先对矩阵做一个编号,且以5*5的矩阵为例(给这个矩阵起个名字叫M1):

M1

0 1 2 3 4

1 2 3 4 5

2 3 4 5 6

3 4 5 6 7

4 5 6 7 8

从左上(0)走到右下(8)共需要走8步(2*5-2)。我们设所走的步数为s。因为限定了只能向右和向下走,因此无论如何走,经过8步后(s = 8)都将走到右下。而DP的状态也是依据所走的步数来记录的。

再来分析一下经过其他s步后所处的位置,根据上面的讨论,可以知道:

  • 经过8步后,一定处于右下角(8);
  • 那么经过5步后(s = 5),肯定会处于编号为5的位置;
  • 3步后肯定处于编号为3的位置;
  • s = 4的时候,处于编号为4的位置,此时对于方格中,共有5(相当于n)个不同的位置,也是所有编号中最多的。

故推广来说,对于n*n的方格,总共需要走2n - 2步,且当s = n - 1时,编号为n个,也是编号数最多的。

如果用DP[s,i,j]来记录2次所走的状态获得的最大值,其中s表示走s步,i和j分别表示在s步后第1趟走的位置和第2趟走的位置。

为了方便描述,再对矩阵做一个编号(给这个矩阵起个名字叫M2):

M2

0 0 0 0 0

1 1 1 1 1

2 2 2 2 2

3 3 3 3 3

4 4 4 4 4

把之前定的M1矩阵也再贴下:

M1

0 1 2 3 4

1 2 3 4 5

2 3 4 5 6

3 4 5 6 7

4 5 6 7 8 我们先看M1,在经过6步后,肯定处于M1中编号为6的位置。而M1中共有3个编号为6的,它们分别对应M2中的2 3 4。故对于M2来说,假设第1次经过6步走到了M2中的2,第2次经过6步走到了M2中的4,DP[s,i,j] 则对应 DP[6,2,4]。由于s = 2n - 2,0 = 0) && (y1 < n) && (y2 >= 0) && (y2 < n));
}

int GetValue(int step, int x1, int x2, int n) //处理越界 不存在的位置 给负无穷的值
{
return IsValid(step, x1, x2, n) ? dp[step][x1][x2] : (-inf);
}

//状态表示dp[step][i][j] 并且i