【Leetcode】741. Cherry Pickup

题目地址:

https://leetcode.com/problems/cherry-pickup/

给定一个正方形矩阵,里面的数都是 0 0 0 1 1 1或者 − 1 -1 1 1 1 1代表有樱桃, 0 0 0代表空地,可以走过去, − 1 -1 1代表障碍物,不能走过去。现在要求从 ( 0 , 0 ) (0,0) (0,0)出发,每次只能向右或者向下走,走到 ( n − 1 , n − 1 ) (n-1,n-1) (n1,n1),沿途不可以走到障碍物上,但可以走到空地和樱桃上,如果走到樱桃上则可以采摘,采完后樱桃就被采完了,接着再从 ( n − 1 , n − 1 ) (n-1,n-1) (n1,n1)一路返回 ( 0 , 0 ) (0,0) (0,0),只能向左或者向上走,沿途也可以采摘樱桃,并且不能穿过障碍物。问两趟走完,最多能采多少樱桃。如果无法到达,则返回 0 0 0

主要思路是动态规划。由于同一个樱桃不能采摘多次,所以我们在设计状态的时候,需要将这个情况考虑进去。至于走两趟这个问题,我们可以将返回的那一趟逆置一下,也变成从 ( 0 , 0 ) (0,0) (0,0)出发的一个路径即可。这样就相当于从 ( 0 , 0 ) (0,0) (0,0)出发走两趟,或者相当于两个人同时走。结果是等价的。

设方格矩阵为 g [ i ] [ j ] g[i][j] g[i][j]。接下来,设 f [ k ] [ i 1 ] [ i 2 ] f[k][i_1][i_2] f[k][i1][i2]表示从 ( 0 , 0 ) (0,0) (0,0)出发,第一个人走到 ( i 1 , k − i 1 ) (i_1,k-i_1) (i1,ki1),第二个人走到 ( i 2 , k − i 2 ) (i_2,k-i_2) (i2,ki2)时,采摘的樱桃最大数。我们用 − 1 -1 1来表示走不到那个格子。那么这个采摘樱桃的数量,可能有四种情况:

1、第一个人向右走到 ( i 1 , k − i 1 ) (i_1,k-i_1) (i1,ki1),第二个人也向右走到 ( i 2 , k − i 2 ) (i_2,k-i_2) (i2,ki2)(即”右右“),这种情况下采摘的樱桃数为,如果 i 1 ≠ i 2 i_1\ne i_2 i1=i2,那么 f [ k − 1 ] [ i 1 ] [ i 2 ] + g [ i 1 ] [ k − i 1 ] + g [ i 2 ] [ k − i 2 ] f[k-1][i_1][i_2]+g[i_1][k-i_1]+g[i_2][k-i_2] f[k1][i1][i2]+g[i1][ki1]+g[i2][ki2],否则 f [ k − 1 ] [ i 1 ] [ i 2 ] + g [ i 1 ] [ k − i 1 ] f[k-1][i_1][i_2]+g[i_1][k-i_1] f[k1][i1][i2]+g[i1][ki1],这样就表达了“同一个樱桃不能采摘多次”这个意思。

2、第一个人向下走到 ( i 1 , k − i 1 ) (i_1,k-i_1) (i1,ki1),第二个人也向右走到 ( i 2 , k − i 2 ) (i_2,k-i_2) (i2,ki2)(即”下右“),这种情况下采摘的樱桃数为,如果 i 1 ≠ i 2 i_1\ne i_2 i1=i2,那么 f [ k − 1 ] [ i 1 − 1 ] [ i 2 ] + g [ i 1 ] [ k − i 1 ] + g [ i 2 ] [ k − i 2 ] f[k-1][i_1-1][i_2]+g[i_1][k-i_1]+g[i_2][k-i_2] f[k1][i11][i2]+g[i1][ki1]+g[i2][ki2],否则 f [ k − 1 ] [ i 1 − 1 ] [ i 2 ] + g [ i 1 ] [ k − i 1 ] f[k-1][i_1-1][i_2]+g[i_1][k-i_1] f[k1][i11][i2]+g[i1][ki1]

…以此类推。这样一路递推下去,最后 f [ 2 n − 2 ] [ n − 1 ] [ n − 1 ] f[2n-2][n-1][n-1] f[2n2][n1][n1]即为所求结果。但是,还有几个细节需要处理:
第一,如果 g [ i ] [ j ] = − 1 g[i][j]=-1 g[i][j]=1,那么这个格子不允许走,需要特殊处理。如果其中一个人走到了障碍物,可以直接标记 f = − 1 f=-1 f=1
第二,如果在第 k k k层的某方格上,其上一层 f [ k − 1 ] f[k-1] f[k1]的这个方格和这个方格的左边、上边和左上都是 − 1 -1 1,其实说明了这个方格事实上是走不到的,也要直接标记为 − 1 -1 1

代码如下:

class Solution {
 public:
  int cherryPickup(vector<vector<int>>& g) {
    int n = g.size(), f[2 * n - 1][n][n];
    memset(f, -1, sizeof f);
    for (int k = 0; k < 2 * n - 1; k++)
      for (int i1 = 0; i1 < n; i1++)
        for (int i2 = 0; i2 < n; i2++) {
          int j1 = k - i1, j2 = k - i2;
          if (0 <= j1 && j1 < n && 0 <= j2 && j2 < n && ~g[i1][j1] && ~g[i2][j2]) {
            if (!k) f[0][0][0] = g[0][0];
            else {
              int t = -1;
              // 左左
              if (j1 && j2) t = max(t, f[k - 1][i1][i2]);
              // 上上
              if (i1 && i2) t = max(t, f[k - 1][i1 - 1][i2 - 1]);
              // 左上
              if (j1 && i2) t = max(t, f[k - 1][i1][i2 - 1]);
              // 上左
              if (i1 && j2) t = max(t, f[k - 1][i1 - 1][i2]);
              if (~t) {
                t += g[i1][j1] + g[i2][j2];
                if (i1 == i2 && j1 == j2) t -= g[i1][j1];
                f[k][i1][i2] = t;
              }
            }
          }
        }

    return ~f[2 * n - 2][n - 1][n - 1] ? f[2 * n - 2][n - 1][n - 1] : 0;
  }
};

时空复杂度 O ( n 3 ) O(n^3) O(n3)。算法正确性可由数学归纳法得。

你可能感兴趣的:(LC,贪心,动态规划与记忆化搜索,算法,动态规划,leetcode,c++)