【力扣】roadmap 1937. 扣分后的最大得分

14 阅读1分钟

题目描述

image.png

思路

题目描述很清楚,容易理解,但是如果写暴力的话...

我们假设n行m列,定义答案矩阵dp[n][m]

那么我们需要枚举每一行;更新当前行的每一个元素的最大分值的时候需要上一行的每一个元素的最大分值情况,这样合算下来就是O(nm2)O(nm^2)的复杂度,包超时的。(题目中nm105nm \le 10^5,显然让你的复杂度为这个量级,根据复杂度猜一下算法是啥?必然是dp。)

不妨先把最朴素的状态转移方程写一下,这个不难 :


定义dp[i][j]表示自上向下选择,并且选到第i行取走cell[i][j]后,能得到的最大值。

dp[i][j]=max(dp[i1][c]jc)+cell[i][j]dp[i][j] = max(dp[i-1][c] - |j-c|) + cell[i][j]

其中c从0~m-1循环。


从这个朴素的解法上,我们的思路在于,你在刷第i行的答案的时候,你是先选择i-1行的最大转移位置,再更新第i行的第j个位置得到该位置最大值。换句话说,你为了更新一个格子dp[i][j],还把i-1行全部遍历了一遍。那你刷完第i行,那复杂度不就O(m2)O(m^2)了吗?

解题的关键在于,事实上我们可以一边选最大值同时刷答案(当然,刷答案是分阶段的,第一次刷完dp[i][j]未必是最大值,第二次刷到dp[i][j]才是最大值)

我们将上面的公式分情况讨论:

  • cjc \le j的时候,上面的朴素转移方程可以改写成
dp[i][j]=max(dp[i1][c]+c)+cell[i][j]jdp[i][j] = max(dp[i-1][c] + c) + cell[i][j] - j

你发现这个方程有点意思,当你在枚举到j这一列的时候,事实上max(dp[i-1][c] + c)你可以捎带手给算出来,因为你for循环的时候就是从0遍历到j的,这片儿下标不都是符合cjc \le j条件的吗?O(m)O(m)代价可以更新一遍dp[i][j],但是这不算完,因为你还没考虑另一个情况

  • c>jc \gt j的时候,上面的朴素转移方程可以改写成
dp[i][j]=max(dp[i1][c]c)+cell[i][j]+jdp[i][j] = max(dp[i-1][c] - c) + cell[i][j] + j

同理,再用类似的代码把dp[i][j]刷一遍,这才算得到正确的答案。

代码

class Solution:
    def maxPoints(self, points: List[List[int]]) -> int:
        n , m = len(points) , len(points[0])
        dp = [0] * m 
        for i in range(n) :
            f = [0] * m 
            left_best = -inf 
            for j in range(m) :
                left_best = max(left_best , dp[j] + j) 
                f[j] = max(f[j] , left_best + points[i][j] - j) 
            
            right_best = -inf 
            for j in range(m-1,-1,-1) :
                right_best = max(right_best , dp[j] - j) 
                f[j] = max(f[j] , right_best + points[i][j] + j) 
            dp = f 
        return max(dp)