算法训练——02拿金币
问题描述:
有一个N x N的方格,每一个格子都有一些金币,只要站在格子里就能拿到里面的金币。你站在最左上角的格子里,每次可以从一个格子走到它右边或下边的格子里。请问如何走才能拿到最多的金币。
输入格式:
第一行输入一个正整数n;
以下n行描述该方格。金币数保证是不超过1000的正整数。
输出格式:
最多能拿金币数量。
样例输入:
3
1 3 3
2 2 2
3 1 2
样例输出:
11
数据规模和约定:
n<=1000
附上代码:
#拿金币
n=int(input())
nums=[]
dp=[]
for i in range(n):
#读取每一行的金币数目
nums.append(list(map(int,input().split())))
for i in range(n):
#创建一个n*n的全为0的二维数组
dp.append([0]*n)
#将起始位置,即最左上角的金币值赋给dp[0][0]
#dp[i][j]代表移动至二维数组中第i行,第j列的位置时,此时存储累加的最大金币数目
#num[i][j]表示移动至某位置盒子里面拥有的金币数目
dp[0][0]=nums[0][0]
#确定初始范围
for i in range(1,n):
#只能往下边走,表示最左边的一列的状态值,
# 当前状态等于上一个状态(前面的路径上所有金币之和)加上现在格子里面的金币数
dp[i][0]=dp[i-1][0]+nums[i][0]
#只能往右边走,表示最上面一行当前状态的金币之和
# (最大金币数——只有一条路,也不得不大)
dp[0][i]=dp[0][i-1]+nums[0][i]
#状态转移函数
for i in range(1,n):
for j in range(1,n):
#最优子结构,表示当前路径金币之和的最大值
dp[i][j]=max(dp[i-1][j],dp[i][j-1])+nums[i][j]
print(dp[n-1][n-1])
运行结果:
有关拿金币问题的思考:
本题不是简单的比较当处于某位置时,比较其右边和下边的金币数值,然后选择金币数值大的那条路径,而是要求解一条最佳路径,使得这条路径上所获得的金币数量最多;这属于一个典型的动态规划问题,因此用dp算法进行求解。
DP算法核心思想:
确定二维数组大小为n*n,将某位置的金币数存入nums[i] [j]中,当移动至二维数组中第i行,第j列的位置时,可以将此时存储累加的最大金币数存入dp[i] [j]。
边界
由于题目规定只能往右走或者往下走,在确定了从最左上角开始走时,我们可以令dp[0] [0]=nums[0] [0];这个时候就有两个特殊的边界情况需要处理,分别是第一行(dp[0] [i])和第一列(dp[i] [0])。
1、第一行(dp[0] [i])
因为题目规定,只能向下或者向右走,而第一行的格子,没有上一行,因此,只能说是由上一状态向右移动,这样一层一层的深入(个人感觉,有点套娃的感觉),我们可以发现,最上面的格子只能是横着走,即dp[0] [i]=dp[0] [i-1]+nums[0] [i],表示该种边界状态的最大金币数。(因为只有一条路,所以一定是最大)
2、第一列(dp[i] [0])
和上面的边界情况类似,如果出现在第一列,没有左边的列,只能由上一状态不断劲直向下,即dp[i] [0]=dp[i-1] [o]+num[i] [0]。
状态转移函数
如上图所示,有C->A和B->A两条路径都可以抵达A,为了获得最大金币数,我们可以通过判断B和C哪个存储的“路径金币之和”最大,我们用dp[i] [j]表示从初始位置到该位置([i] [j],第i行,第j列)存储累加的最大金币数。即dp[i] [j]=max(dp[i-1] [j],dpi] [j-1])+num[i] [j]。