太爽了!做完这道题,让我感觉就像是斩杀了一条大龙!历时72天,分3次花掉30小时。终获突破!
零、题目
3418. 机器人可以获得的最大金币数
给你一个 m x n
的网格。一个机器人从网格的左上角 (0, 0)
出发,目标是到达网格的右下角 (m - 1, n - 1)
。在任意时刻,机器人只能向右或向下移动。
网格中的每个单元格包含一个值 coins[i][j]
:
- 如果
coins[i][j] >= 0
,机器人可以获得该单元格的金币。 - 如果
coins[i][j] < 0
,机器人会遇到一个强盗,强盗会抢走该单元格数值的 绝对值 的金币。
机器人有一项特殊能力,可以在行程中 最多感化 2个单元格的强盗,从而防止这些单元格的金币被抢走。
注意:机器人的总金币数可以是负数。
返回机器人在路径上可以获得的 最大金币数 。
一、思路诞生
最开始没有思路。对于动态规划做了很多题。
动态规划现在总结为五大步:
0.从(0,0)之类的挪到(1,1)方便使用
1.确定能影响结果的自变量
2.定义一个dp,当然可以是多元函数。C语言支持多维数组。
给出dp[i][j]的含义。用中文
3.写出状态转移方程。根据最后一个,通常是最后一个,或者右下角
4.求解结果:取出最后一个(或者最后一叠)中的最值。
二、本题思路
由于读完题后,发现这里有几个关键的自变量。
一个是二维数组的坐标(m,n)
另一个是使用感化的次数k
由于金币相当于是已知信息,并且这个信息不受其他信息的影响是绝对不变的。
而要求解问题时,某个具体坐标的具体感化次数都是会受到影响的。
倘若没有感化次数,也就是每资格感化,绝对不变,也就是不受其他信息的影响,那就没必要使用动态规划函数里面的一个变量。
于是就开始写状态转移方程
dp(m,n,k)表示从(1,1)到(m,n)恰好使用k次感化所能获得的最大金币数。(到4.得到结果那里发现这个定义不够准确,但是状态转移方程那里倒是对了)
当然也可以用别的方式定义,但是状态方程会变化。这种我自己觉得比价好想。
而后就是从k=0开始考量,一点一点考量。k=1开始考量。一点一点的。
最后想出了状态转移方程的全部(见草稿纸)
然后中间有一点卡住了,就是一直在想这一格是感化还是不感化呢。后来看了题干,恍然大悟,大于等于0的时候,根本不能感悟。只有小于0的时候才能感悟。于是,就应该根据这个格子金币是否小于0做出判断。
三、开始写代码
分为四部分:数据读入并迁移、数据初始化、状态转移、得到结果
1.数据读入并迁移
力扣里面int maximumAmount(int** coins, int coinsSize, int* coinsColSize)的含义就是
数组,行长度(有多少行),列长度(有多少列)
int extGrid[600][600];int i, j, k;int m = coinsSize; int n = *coinsColSize;for (i = 0; i < m; i++) {for (j = 0; j < n; j++) {extGrid[i + 1][j + 1] = coins[i][j];//printf("%d ",coins[i][j]);}}for (i = 1; i <= m; i++) {for (j = 1; j <= n; j++) {printf("%d ", extGrid[i][j]);}puts("");}
这样就可以完成迁移并打印检查了。
2.数据初始化
数据初始化就是考虑第0行和第0列。这是一个常规操作。但是由于这里金币范围是[-1000,1000]设置一个负无穷什么的就可以。但是我这里做了一小点创新。就是我使用了一种自定义的最大值获取,我称为Emax,存在最大值existMax。因为状态转移方程里面,会看到如果这种情况不存在的话,那就不用考虑。而最后放入dp格子中时,肯定要加入extGrid[i][j],那么这些不存在的情况其实就是0.所以来看一下Emax的实现
int isAlive(int mem[3]) {int r = mem[0];int q = mem[1];if (r == 0) {return 0;}if (q == 0) {return 0;}return 1;
}
int getNum(int mem[3]) {int r = mem[0];int q = mem[1];int s = mem[2];return dp[r][q][s];
}
int Emax(int mem1[3], int mem2[3]) {int ali1 = isAlive(mem1);int ali2 = isAlive(mem2);if (!ali1&&!ali2) {return 0;//都不存在,该值为0}if (!ali1 && ali2) {return getNum(mem2);}if (ali1 && !ali2) {return getNum(mem1);}//都存在,返回较大的return fmax(getNum(mem1), getNum(mem2));
}
3.状态转移
这一步的关键就是准确列出所有情况。分类讨论。
这里有两点创新。
一个是因为每次写dp[i][j][k]挺累的,不妨弄一个member成员,用来方便书写。每次只要传member就可以了。另外就是可以看到先用Emax取存在的结果,再用Emax得到的结果使用库函数fmax取最大值得到结果。
for (i = 1; i <= m; i++) {for (j = 1; j <= n; j++) {if (extGrid[i][j] >= 0) {for (k = 0; k <= 2; k++) {int mem1[3] = { i - 1,j,k };int mem2[3] = { i,j - 1,k };dp[i][j][k] = Emax(mem1, mem2)+extGrid[i][j];}}else {//dp(m,n,0)k = 0;int mem1[3] = { i - 1,j,k };int mem2[3] = { i,j - 1,k };dp[i][j][k] = Emax(mem1, mem2) + extGrid[i][j];//dp(m,n,1)k = 1;int bmem1[3] = { i - 1,j,k };int bmem2[3] = { i,j - 1,k };k = 0;int bmem3[3] = { i - 1,j,k };int bmem4[3] = { i,j - 1,k };int t1= Emax(bmem1, bmem2) + extGrid[i][j];int t2 = Emax(bmem3, bmem4);k = 1;dp[i][j][k] = fmax(t1,t2) ;//dp(m,n,2)k = 2;int cmem1[3] = { i - 1,j,k };int cmem2[3] = { i,j - 1,k };k = 1;int cmem3[3] = { i - 1,j,k };int cmem4[3] = { i,j - 1,k };t1 = Emax(cmem1, cmem2) + extGrid[i][j];t2 = Emax(cmem3, cmem4);k = 2;dp[i][j][k] = fmax(t1, t2);}}}
4.得到结果
这里就只需要取最后的位置,摘取胜利果实就可以了。
return fmax(fmax(dp[m][n][0], dp[m][n][1]), dp[m][n][2]);
不过,需要注意的是,感化次数为2,自然一定比恰使用感化次数为1的金币数多。因为多一次机会。不过是否每次都能有2次感化的机会呢?需要得有2个负数才可以。否则2次感化就是抄的之前的结果。如果之前一直为0的话。那就出故障了。所以还是应该fmax最好。不过巧了。这里没有影响。因为实际上我们实现的代码dp的含义是,感化次数最多为k的时候,所能得到的最大金币数量。这样状态转移方程才合理。
四、提交改BUG
0.本地vs2022调试失败
出现栈溢出错误。上网学习,发现改一下就好了。
dp[600][600][3]这是数组,肯定是在栈上的。
如果用malloc,就会在堆上,但是会很麻烦。
感谢下面的博客教我计算。格子里的数字单位是字节。差不多10MB的内存能跑起来,100MB或者200MB肯定就够用了
VS运行时报错:未经处理的异常-CSDN博客
修好啦!
1.小于等于号漏等于
分类讨论那里。就需要注意不能只是小于。只小于最后的结果,就是输出的k=0,k=1全部都是0.用调试,调了一下感觉没那么方便但是有一些一调试就是一下就能看出来。其实还是看调试那里,感觉监视窗口里面i,j,k的值不变,就哪里有问题哪里打断点,比较快地解决了。
2.在372个测试样例时出现了超时
一交,对了300多个。我很激动。说明算法对了。哪里还需要小小优化一下。
一种是算法不是最快的,但是我这O(N^2)已经最快了。不是这个原因
另一种是差个系数。我觉得是这个。
①首先调整了isAlive代码尝试变快
收效甚微
②其次问chatGLM智谱清言
告诉我了这个。我觉得ptintf这个I/O输出很有道理(下图第4条),就试了一下。
结果没想到从790ms一下子变成了27ms,快了将近30倍。我的妈呀。
这个一改。就过了。
五、完整代码
通关截图:
#include <stdio.h>
#include <malloc.h>
#include <math.h>
int dp[600][600][3];//0,1,2表示感化次数
int isAlive(int mem[3]) {int r = mem[0];int q = mem[1];if (r == 0) {return 0;}if (q == 0) {return 0;}return 1;
}
int getNum(int mem[3]) {int r = mem[0];int q = mem[1];int s = mem[2];return dp[r][q][s];
}
int Emax(int mem1[3], int mem2[3]) {int ali1 = isAlive(mem1);int ali2 = isAlive(mem2);if (!ali1&&!ali2) {return 0;//都不存在,该值为0}if (!ali1 && ali2) {return getNum(mem2);}if (ali1 && !ali2) {return getNum(mem1);}//都存在,返回较大的return fmax(getNum(mem1), getNum(mem2));}
int maximumAmount(int** coins, int coinsSize, int* coinsColSize) {int extGrid[600][600];int i, j, k;int m = coinsSize; int n = *coinsColSize;for (i = 0; i < m; i++) {for (j = 0; j < n; j++) {extGrid[i + 1][j + 1] = coins[i][j];//printf("%d ",coins[i][j]);}}for (i = 1; i <= m; i++) {for (j = 1; j <= n; j++) {printf("%d ", extGrid[i][j]);}puts("");}for (i = 1; i <= m; i++) {for (j = 1; j <= n; j++) {if (extGrid[i][j] >= 0) {for (k = 0; k <= 2; k++) {int mem1[3] = { i - 1,j,k };int mem2[3] = { i,j - 1,k };dp[i][j][k] = Emax(mem1, mem2)+extGrid[i][j];}}else {//dp(m,n,0)k = 0;int mem1[3] = { i - 1,j,k };int mem2[3] = { i,j - 1,k };dp[i][j][k] = Emax(mem1, mem2) + extGrid[i][j];//dp(m,n,1)k = 1;int bmem1[3] = { i - 1,j,k };int bmem2[3] = { i,j - 1,k };k = 0;int bmem3[3] = { i - 1,j,k };int bmem4[3] = { i,j - 1,k };int t1= Emax(bmem1, bmem2) + extGrid[i][j];int t2 = Emax(bmem3, bmem4);k = 1;dp[i][j][k] = fmax(t1,t2) ;//dp(m,n,2)k = 2;int cmem1[3] = { i - 1,j,k };int cmem2[3] = { i,j - 1,k };k = 1;int cmem3[3] = { i - 1,j,k };int cmem4[3] = { i,j - 1,k };t1 = Emax(cmem1, cmem2) + extGrid[i][j];t2 = Emax(cmem3, cmem4);k = 2;dp[i][j][k] = fmax(t1, t2);}}}for (k = 0; k <= 2; k++) {printf("k is %d\n", k);for (i = 1; i <= m; i++) {for (j = 1; j <= n; j++) {//printf("dp[%d][%d][%d]=%d\n", i, j, k, dp[i][j][k]);printf("%d ", dp[i][j][k]);}puts("");}}return fmax(fmax(dp[m][n][2], dp[m][n][1]), dp[m][n][2]);
}
int main() {int* arr1 = (int*)malloc(sizeof(int) * 3);int *arr2= (int*)malloc(sizeof(int) * 3);int* arr3 = (int*)malloc(sizeof(int) * 3);int coins2 [3][3] = {{0, 1, -1},{1, -2, 3},{2, -3, 4}};int** coins = (int**)malloc(sizeof(int*) * 3);coins[0] = arr1; coins[1] = arr2; coins[2] = arr3;int i, j;for (i = 0; i < 3; i++) {for (j = 0; j < 3; j++) {coins[i][j] = coins2[i][j];}}int col = 3;int ans = maximumAmount(coins,3,&col);printf("\nans is %d\n", ans);return 0;
}
补负一、失败的思路
因为代码太复杂,写初始化卡死了。初始化想不通。
失败的代码。从失败中获得了启发。想到了Emax函数
typedef struct Square{int a0;int a1;int a2;int negativeMostMax;int negativeSecondMax;int money;
}sq;
int fmaxnum(int a, int b){if(a==b){return 3;}if(a>b){return 1;}return 2;
}
void changeNegative(sq lastSq,sq * square){int min1=lastSq.negativeMostMax;int min2=lastSq.negativeSecondMax;int m=square->money;if(m<min1){min2=min1;min1=m;}else if(m<min2){min2=m;}square->negativeMostMax=min1;square->negativeSecondMax=min2;
}
void showSquareArr(sq sqArr[600][600],int m,int n){puts("");puts("k=0");int i,j;for(i=0;i<=m;i++){for(j=0;j<=n;j++){printf("%d ",sqArr[i][j].a0);}puts("");}puts("");puts("");puts("negativeMostMax");for(i=0;i<=m;i++){for(j=0;j<=n;j++){printf("%d ",sqArr[i][j].negativeMostMax);}puts("");}puts("");puts("");puts("negativeSecondMax");for(i=0;i<=m;i++){for(j=0;j<=n;j++){printf("%d ",sqArr[i][j].negativeSecondMax);}puts("");}puts("");// puts("");// puts("k=1");// for(i=0;i<=m;i++){// for(j=0;j<=n;j++){// printf("%d ",sqArr[i][j].a1);// }// puts("");// }// puts("");// puts("");// puts("k=2");// for(i=0;i<=m;i++){// for(j=0;j<=n;j++){// printf("%d ",sqArr[i][j].a2);// }// puts("");// }// puts("");// puts("");// puts("moneyGrid");// for(i=0;i<=m;i++){// for(j=0;j<=n;j++){// printf("%d ",sqArr[i][j].money);// }// puts("");// }// puts("");}int maximumAmount(int** coins, int coinsSize, int* coinsColSize) {int i,j;int i_,j_;sq extGrid[600][600]={9};//11:10开始思考//11:20有思路,开始验证//11:20-11:40更新思路,开始码代码//13;13 了解leetcode规则//1413开始码字int m=coinsSize;int n=*coinsColSize;//showSquareArr(extGrid,m,n);//矩阵拓展.存储moneyfor(i=0,i_=1;i<m;i++,i_++){for(j=0,j_=1;j<n;j++,j_++){extGrid[i_][j_].money=coins[i][j];}}//showSquareArr(extGrid,m,n);//进行矩阵初始化。(k=0)extGrid[1][1].a0=extGrid[1][1].money;//初始化第一列j=1;for(i=2;i<=m;i++){extGrid[i][j].a0=extGrid[i-1][j].a0+extGrid[i][j].money;}//初始化第一行i=1;for(j=2;j<=n;j++){extGrid[i][j].a0=extGrid[i][j-1].a0+extGrid[i][j].money;}//状态转移for(i=2;i<=m;i++){for(j=2;j<=n;j++){extGrid[i][j].a0=fmax(extGrid[i][j-1].a0,extGrid[i-1][j].a0)+extGrid[i][j].money;}}//进行矩阵初始化。(最小值的建立)extGrid[1][1].negativeMostMax=extGrid[1][1].money;//注意第一行第一列只有一个有效值。第一行第二列有2个有效值int mt;mt=extGrid[1][2].money;if(mt<extGrid[1][1].negativeMostMax){extGrid[1][2].negativeSecondMax=extGrid[1][1].negativeMostMax;extGrid[1][2].negativeMostMax=mt;}else{extGrid[1][2].negativeMostMax=extGrid[1][1].negativeMostMax;extGrid[1][2].negativeSecondMax=mt;}//初始化第一行.j从3开始i=1;for(j=3;j<=n;j++){changeNegative(extGrid[i][j-1],&extGrid[i][j]);}//注意第一行第一列只有一个有效值。第二行第一列有2个有效值mt=extGrid[2][1].money;if(mt<extGrid[1][1].negativeMostMax){extGrid[2][1].negativeSecondMax=extGrid[1][1].negativeMostMax;extGrid[2][1].negativeMostMax=mt;}else{extGrid[2][1].negativeMostMax=extGrid[1][1].negativeMostMax;extGrid[2][1].negativeSecondMax=mt;}//初始化第一列.i从3开始j=1;for(i=3;i<=m;i++){changeNegative(extGrid[i-1][j],&extGrid[i][j]);}//状态转移for(i=2;i<=m;i++){for(j=2;j<=n;j++){int num=fmaxnum(extGrid[i][j-1].a0,extGrid[i-1][j].a0);extGrid[i][j].a0=fmax(extGrid[i][j-1].a0,extGrid[i-1][j].a0)+extGrid[i][j].money;}}showSquareArr(extGrid,m,n);return 0;
}
感谢大家的观看和喜欢!祝读者朋友们也屠龙痛快!