[Dynamic programming--0-1 knapsack variant] 741. Picking cherries

topic description

( Difficult ) An N x N grid (grid)represents a cherry field, and each grid is represented by one of the following three numbers:

  • 0 means the grid is empty, so you can walk through it.
  • 1 means that there is a cherry in this grid, you can pick the cherry and pass through it.
  • -1 means that there are thorns in this grid, blocking your way.

Your task is to pick as many cherries as possible while adhering to the following rules:

  • Starting from position (0, 0) and finally reaching (N-1, N-1), you can only go down or to the right, and you can only pass through valid grids (that is, you can only pass through grids with a value of 0 or 1 );
  • When you reach (N-1, N-1), you have to continue walking until you return to (0, 0), you can only go up or to the left, and you can only go through valid grids;
  • When you pass a grid and this grid contains a cherry, you will pick a cherry and this grid will become empty (value becomes 0);
  • If there is no path that can be passed between (0, 0) and (N-1, N-1), then no cherry can be picked.

Example:

输入: grid =
[[0, 1, -1],
 [1, 0, -1],
 [1, 1,  1]]
输出: 5
解释: 
玩家从(0,0)点出发,经过了向下走,向下走,向右走,向右走,到达了点(2, 2)
在这趟单程中,总共摘到了4颗樱桃,矩阵变成了[[0,1,-1],[0,0,-1],[0,0,0]]
接着,这名玩家向左走,向上走,向上走,向左走,返回了起始点,又摘到了1颗樱桃。
在旅程中,总共摘到了5颗樱桃,这是可以摘到的最大值了。

problem solving ideas

On August 17th, the 300-point Huawei machine test question 3, the original question was about the pier, but it was actually related to picking cherries.

The title requires going right or down, and returning up or left, which is actually equivalent to going forward twice . I personally think that this equivalent conversion is the difficulty of this question. If you understand this point, it will be easier to implement it through code later.

Then this question at this time is very similar to the 0-1 knapsack problem, or a variant of the knapsack problem. The size of the backpack is the steps needed to go from (0, 0) to (N - 1, N - 1) 2 * (N - 1). The size of the object is the steps needed to walk through the current cherry field 1, and the value is the number of cherries it owns. The cherries in each cherry field can only be picked once, which is 0-1 knapsack, how many cherries can a knapsack with a capacity of 0 hold 2 * (N - 1)under the action rules required by the question.

Considering such a problem again, how to realize the logic that cherries in the same cherry field can only be picked once?

In order to solve this problem and facilitate the design and traversal of the subsequent dp array, we consider performing two advances at the same time, that is, two people start from (0, 0) and go to (N - 1, N - 1) at the same time . When two people meet, only increase the number of cherries once.

Why did the two meet? Don't you need to think about the two arriving at the same cherry place one after the other? The fact is that this situation is impossible, and the two must visit the same cherry field at the same time. Because under the rule of action that can only go to the right and down, the distance between each cherry field and the starting point (0, 0) is certain kand unique . It can be understood as the number of steps . If kthe first person arrives at point A at time , then this is the only chance for the second person to reach K,K > kpoint KA. kStep A.

So for the question of how to realize the logic that cherries in the same cherry field can only be picked once, we only need to kjudge whether they meet when traversing.

The rest is the common dynamic programming four steps:

  • Define the dp array and its meaning: three-dimensional dp array, dp[k][x1][x2]. Among them kis the current k step, x1is the x value of the first person, and its corresponding y1is k - x1. Empathy, understand x2. Then dp[k][x1][x2]the meaning is that after taking k steps, the two people respectively reach (x1, k - x1) and (x2, k - x2) the maximum number of cherries picked.
  • Determine the state transition formula: (x1, x2) at this time may be obtained by two people going to the right at the same time , going down at the same time, one right and one right , and one down and one right respectively. Corresponding to dp[k - 1][x1][x2], dp[k - 1][x1 - 1][x2 - 1], dp[k - 1][x1][x2 - 1], dp[k - 1][x1 - 1][x2]. In the follow-up, it is necessary to judge whether they meet. If they meet, they only need to add the number of cherries once. If they do not meet, add them under the premise of the above four possible maximum valuesgrid[x1][k - x1] + grid[x2][k - x2] .
  • Initialize the dp array: Judging according to the state transition formula, all values ​​ultimately depend on the value of dp[0][0][0], which is equal to grid[0][0]. In addition, all the remaining positions are set to INT_MINbe convenient for processing when thorns are encountered, and can be continueskipped, which is equivalent to setting a penalty item for places with thorns, so that they will automatically fail in the comparison of participating in the maximum value.
  • Determine the traversal order of the dp array: the current point depends on the top and left of the point, so the traversal is from top to bottom and from left to right.

The specific code implementation is given below.

Code

class Solution {
    
    
    const int dx1[4] = {
    
    0, -1, 0, -1};
    const int dx2[4] = {
    
    0, -1, -1, 0};
public:
    int cherryPickup(vector<vector<int>>& grid) {
    
    
        int n = grid.size();
        //1. 定义dp[k][x1][x2],两人同时从(0,0)出发,各走k步,分别到达(x1, k - x1)、(x2, k - x2)时所摘到的樱桃最大值
        vector<vector<vector<int>>> dp(2 * n - 1, vector<vector<int>>(n, vector<int>(n, INT_MIN)));
        //2. 确定状态转移方程,见上文
        //3. 初始化dp数组
        dp[0][0][0] = grid[0][0];
        //4. 确定遍历顺序
        for(int k = 1; k < 2 * n - 1; k++){
    
    
            for(int x1 = max(k - n + 1, 0); x1 <= min(k, n - 1); x1++){
    
    
                if(grid[x1][k - x1] == -1) continue;
                for(int x2 = x1; x2 <= min(k, n - 1); x2++){
    
    
                    if(grid[x2][k - x2] == -1) continue;
                    for(int i = 0; i < 4; i++){
    
    
                        int nx1 = x1 + dx1[i];
                        int nx2 = x2 + dx2[i];
                        if(nx1 >= 0 && nx2 >= 0){
    
    
                            dp[k][x1][x2] = max(dp[k][x1][x2], dp[k - 1][nx1][nx2]);
                        }
                    }
                    if(x1 != x2) dp[k][x1][x2] += (grid[x1][k - x1] + grid[x2][k - x2]);
                    else dp[k][x1][x2] += grid[x1][k - x1];
                }
            }
        }
        return max(dp[2 * n - 2][n - 1][n - 1], 0);//存在起点与终点之间完全被荆棘隔开,但凡通过荆棘的路径其值都为负数。
    }
};

Running results:
insert image description here
In the future, you can consider using rolling arrays to optimize the memory usage. Specifically, optimize the first dimension, as follows:

class Solution {
    
    
    const int dx1[4] = {
    
    0, -1, 0, -1};
    const int dx2[4] = {
    
    0, -1, -1, 0};
public:
    int cherryPickup(vector<vector<int>>& grid) {
    
    
        int n = grid.size();
        //1. 定义dp[x1][x2],两人同时从(0,0)出发,分别到达(x1, k - x1)、(x2, k - x2)时所摘到的樱桃最大值
        vector<vector<int>> dp(n, vector<int>(n, INT_MIN));
        //2. 确定状态转移方程,见上文
        //3. 初始化dp数组
        dp[0][0] = grid[0][0];
        //4. 确定遍历顺序
        for(int k = 1; k < 2 * n - 1; k++){
    
    
            for(int x1 = min(k, n - 1); x1 >= max(k - n + 1, 0); x1--){
    
    
                for(int x2 = min(k, n - 1); x2 >= x1; x2--){
    
    
                    //对荆棘点的处理发生了改变
                    if(grid[x1][k - x1] == -1 || grid[x2][k - x2] == -1){
    
    
                        dp[x1][x2] = INT_MIN;
                        continue;
                    }
                    int res = INT_MIN;
                    for(int i = 0; i < 4; i++){
    
    
                        int nx1 = x1 + dx1[i];
                        int nx2 = x2 + dx2[i];
                        if(nx1 >= 0 && nx2 >= 0){
    
    
                            res = max(res, dp[nx1][nx2]);
                        }
                    }
                    if(x1 != x2) res += (grid[x1][k - x1] + grid[x2][k - x2]);
                    else res += grid[x1][k - x1];
                    dp[x1][x2] = res;
                }
            }
        }
        return max(dp[n - 1][n - 1], 0);
    }
};

Optimized:
insert image description here

Guess you like

Origin blog.csdn.net/LogosTR_/article/details/126545137