开一个专栏来记录每日一题,希望有能不看题解顺利一把过的时候
2555. 两个线段获得的最多奖品
本题的主要思想是动态规划,二分法作为一个查找方法。
首先分析问题,两条线段什么时候可以覆盖较多的数字?
尽可能不相交。
其次,考虑这样一个问题:如何的到一个线段所能包含的奖品数?
假设第二段线段的右端点为prize[i],则可以查找出距离这个位置k处的左端点最左边的位置也就是left_bound = prize[i] - k所对应的第一个物品在prize中的索引。那么只需要知道在这个位置处以左有一条线段可以覆盖的最大奖品数,相加即可得到当前位置的奖品总数。所以是一个由前一个状态推导出下一个状态的思路,考虑使用动态规划。
1、dp数组的定义
dp[i]定义为在物品[i]处,有一条长度为k的、右端点不超过i所在位置的线段所能够覆盖的最大奖品数,注意这里不一定要让线段的右端点是i的位置。
2、dp的更新
在i遍历到下一个物品处时,有两个选项:
(1)不取当前的物品,则dp[i] = dp[i-1];
(2)取当前的物品,则dp[i] = i - left_bound + 1,也就是距离i为k的线段所能包含的最左端点的物品的索引,所以两者之差即为从left_bound到i的物品数量。
因此,dp[i] = max(dp[i-1], i - left_bound + 1)。
3、dp的初始化
由于需要使用二分法找左边界,返回的是左边界的值,所以应该是左闭右开区间,也就是右边只能取到i-1的,要包含全部的位置,初始化dp的长度要为prize长度+1;其次,dp[0]表示在[0, 0)位置处的线段覆盖长度,也就是0个元素。
4、遍历顺序
i从0开始从小到大遍历。注意!在推到公式中使用的是i-1但是根据二分法的区间规定,更新的时候用i+1替代i,i替代i-1。
注意事项
1、需要开一个伴随的res来储存全局最大值。
2、res = max(dp[i], dp[left_bound] + i - left + 1),需要加上前一个线段(也就是left_bound处的最大值)。
class Solution(object):
def maximizeWin(self, prizePositions, k):
"""
:type prizePositions: List[int]
:type k: int
:rtype: int
"""
# dp表示一条线段不大于当前位置的最多的可以覆盖的物品数量
dp = [0] * (len(prizePositions)+1)
res = 0
for i in range(len(prizePositions)):
# 二分查找找到prize中,距离当前物品的位置prize[i]为k的第一个物品,在prize中的索引
left_bound = bisect.bisect_left(prizePositions, prizePositions[i]-k)
# 当前所能够覆盖的最大数量是第一个线段+第二个线段
res = max(res, dp[left_bound] + (i - left_bound + 1))
# 更新下一个dp为保留当前值,或者是新线段可以覆盖的数量
dp[i+1] = max(dp[i], i - left_bound + 1)
return res
太难了!