蓝桥杯 ALGO-1006 拿金币 动态规划 双解法 python

题目

蓝桥杯 ALGO-1006 拿金币 动态规划 双解法 python_第1张图片

分析

这是动态规划的典型例题,每走一步选择的应该都是最优子结构,也就是走能拿到金币最多的格子。

这里提供了两种思路解决此问题:递归 与 dp数组。这两种思路可以说是以相反的方式找出最优解,一个自上而下,一个自下而上。

递归

我们在起始点(0,0)位置出发,要么往下走,要么往右走,决定往右走还是往下走的唯一条件就是看哪个位置走到终点能拿到的金币数量多些,哪边拿得多多就选谁。同样的,在下一位置中,还是以同样的方式判断该往下走还是往右走,直到走到终点位置(n-1,n-1)。
蓝桥杯 ALGO-1006 拿金币 动态规划 双解法 python_第2张图片
当走到终点位置(2,2)后(3行3列的话n是3),不再继续进行递归,并将这里的值告诉给(2,1)与 (1,2)两个位置。那么从(2,1)位置走到终点能拿到金币的数量就是1+2 = 3 个,从(1,2)位置走到终点能拿到金币的数量是2+2=4个。对于(1,1)位置来说,它能选下走,走到(1,2)位置,也能往右走走到(2,1)位置,因为从(1,2)位置走到终点位置能拿到金币多些,所以(1,1)位置应该选择从(1,2)方格走到终点,不断重复上面操作,直到起点(0,0)也得出是选下走还是选右走的决定,最后将拿到最多金币的数量作为结果返回。

但是我们看一下上图可以发现,有很多位置重复走过了(比如说(1,1),(2,1),(1,2)),走过的路就没必要再走一遍了,我们可以使用标记数组将记录走过位置以实现剪枝,提高执行效率。

现在我们看一下代码实现:

def dfs(x,y):
    # n行n列范围外的位置没有意义,结束递归
    if x > n-1 or y > n-1:
        return 0
    # 走到终点位置后将终点位置的金币返回
    if x == n-1 and y == n-1:
        return nums[x][y]
    # 如果这个位置没有走过就记录起来,下次再走到同样位置可提前返回结果
    if not used[x][y]:
        # 看往下走能拿金币多还是往右走,哪拿得多就走哪,
        # 然后再将这个位置的金币数加上
        # 那么经过几层递归后可以得到从该位置走到终点最大金币数
        used[x][y] = max(dfs(x+1,y),dfs(x,y+1)) + nums[x][y]
    return used[x][y]
    
    
n = int(input())
nums = []
for i in range(n):
    nums.append(list(map(int,input().split())))
# 定义标记数组
used = [[0 for i in range(n)] for j in range(n)]
# 返回递归结果
print(dfs(0,0))

以上代码我调试过好多次也尝试了好多情况,都没有发现问题…但是提交代码却只能得到30分,而哪些错误解答都是解答错误,而不是运行超时。思索了好久还是没有找到问题所在,而样例vip才能看…这样简直就是死不瞑目

还望大佬赐教,在下感激不尽!

下面DP能拿满分,大家还是看看下面的。

DP

DP数组可以说是动态规划的一大特点,因为太多动态规划的问题能用DP数组来求解了,而其中最常用的便是二维数组。

我们可以用DP[ i ][ j ]表示起点走到第i行第j列位置所能拿到最大金币数量。 注意要与上面回溯算法的思路区分开。

我们知道,一个方格可以从它的上方走来,也能从他的左边走来。至于从哪边走来就要看哪边已经得到的金币数量多些,哪边得到的金币多些就从哪边走来。不断重复以上步骤,当走到终点位置(2,2)时,就能知道能拿到最大金币的数量了。
蓝桥杯 ALGO-1006 拿金币 动态规划 双解法 python_第3张图片
还是看这个图,假设要去位置(1,1),它能从(0,1),(1,0)两个位置走来,要确定从哪边过来,就要看哪边已经得到的金币多些,哪边拿得多就从哪边走来。很显然,(0,1)位置已经拿到了1+3=4个金币会更多些,那么如果要去(1,1)位置的话就要从(0,1)过去。

一般情况下去一个位置就要判断该要从这个位置的的左边过来还是上边,但也少不了特殊情况,那就是最左边和最上边的位置。
蓝桥杯 ALGO-1006 拿金币 动态规划 双解法 python_第4张图片
要想去左边标红的方格,那么必定只能从这些方格的上方过来。(比如说要去(0,2),那必定只能从(0,1)过来)同样的,要去上方标红的方格,那必定只能从这些方格的左边过来。以上两种情况只需要考虑一边即可,

对于中间的方格,需要先知道它左边与上边方格已经得到金币的数量,才能根据哪边拿到更多的金币来选择从哪边走过来。因此,我们在求中间方格能得到的最大金币数量时需要先知道最左边以及最上边所能拿到最大金币数量,才能让中间的方格知道该从左边过来还是上边。

下面看下代码实现:

nums = []
n = int(input())
for i in range(n):
    nums.append(list(map(int, input().split())))

# 初始化dp数组,先全设为0
dp = [[0 for i in range(n)] for j in range(n)]
# 获取起点金币数
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]
        
# 执行完上面程序后 n x n的序列中每个方格能拿到的最大金币数都已经记录下来
# 返回终点位置能拿到的最大金币数
print(dp[n - 1][n - 1])

你可能感兴趣的:(算法,蓝桥,动态规划,蓝桥杯,算法)