LeetCode 135.分发糖果

题目描述

老师想给孩子们分发糖果,有 NN 个孩子站成了一条直线,老师会根据每个孩子的表现,预先给他们评分。

你需要按照以下要求,帮助老师给这些孩子分发糖果:

每个孩子至少分配到 11 个糖果。
相邻的孩子中,评分高的孩子必须获得更多的糖果。
那么这样下来,老师至少需要准备多少颗糖果呢?

示例1

输入: [1,0,2]
输出: 5
解释:

你可以分别给这三个孩子分发 2、1、2 颗糖果。

示例2

输入: [1,2,2]
输出: 4
解释:

你可以分别给这三个孩子分发 1、2、1 颗糖果。
第三个孩子只得到 1 颗糖果,这已满足上述两个条件

题解

题目代码很简单,分别从左往右和从右往左扫描一次,取每个人的最大值相加。重点在于 为什么这么做是对的?

根据贪心的思路,应该确保尽可能每一个人少分糖果,如果符合条件就只给 11 个。而这里限制糖果个数的因素为:相邻的孩子中,评分高的孩子必须获得更多的糖果
也即,每个孩子最终的糖果只依赖于其左右相邻的孩子,与更远的孩子无关。

这部分思路如果按照动态规划,可以理解为:如果孩子比上一个孩子评分高,那么他获得的糖果也应该更高。(逻辑上的上一个,与实际的顺序无关。按照题目要求,为相邻的两个孩子)
因此,实际上状态转移方程为

dp[i]=max({1,score[i1]score[i]dp[i1]+1,score[i1]>score[i],{1,score[i]score[i]dp[i+1]+1,score[i+1]>score[i])dp[i] = max(\begin{cases} 1,&score[i-1] \le score[i]\\dp[i-1]+1,&score[i-1] \gt score[i] \end{cases}, \begin{cases} 1,&score[i-] \le score[i]\\dp[i+1]+1,&score[i+1] \gt score[i] \end{cases})

接下来要考虑的就是转移方程的独立性:为什么可以从两头计算?
上面的转移方程存在一个问题,就是在计算 dp[i]dp[i] 时,需要预先拥有 dp[i1]dp[i-1]dp[i+1]dp[i+1],但这两个数值的计算又依赖于 dp[i]dp[i] 本身。因此常规的动态规划思路无法解决。

但是,如果再次观察这个方程,max 中的两个公式本身是不相关的,也即可以将其分成 dp[i]={1,score[i1]score[i]dp[i1]+1,score[i1]>score[i]dp[i] = \begin{cases} 1,&score[i-1] \le score[i]\\dp[i-1]+1,&score[i-1] \gt score[i] \end{cases}dp[i]={1,score[i]score[i]dp[i+1]+1,score[i+1]>score[i]dp[i] = \begin{cases} 1,&score[i-] \le score[i]\\dp[i+1]+1,&score[i+1] \gt score[i] \end{cases} 两部分。
而这两部分是可以通过状态转移方程按顺序求解的,最后求每个位置的最大值即可。

原始的状态转移方程很容易写出,但是后续的化简可能会较难发现(但是如果真的把这个状态转移方程写出来,并且因为次序问题而觉得动态规划不能解决问题时,还是很可能发现化简方案的)
因此,对于这种问题,写出状态转移方程至关重要,即使状态转移方程可能无法直接求解

代码

class Solution:
    def candy(self, ratings: List[int]) -> int:
        n = len(ratings)
        left = [0] * n
        right = [0] * n
        res = 0
        for i in range(n):
            j = n-i-1
            left[i]  = left[i-1]  + 1 if i != 0   and ratings[i] > ratings[i-1] else 1
            right[j] = right[j+1] + 1 if j != n-1 and ratings[j] > ratings[j+1] else 1    
        for i in range(n):
            res += max(left[i], right[i])
        return res