每日一题 好子数组的最大分数
给你一个整数数组 nums
(下标从 0 开始)和一个整数 k
。
一个子数组 (i, j)
的 分数 定义为 min(nums[i], nums[i+1], ..., nums[j]) * (j - i + 1)
。一个 好 子数组的两个端点下标需要满足 i <= k <= j
。
请你返回 好 子数组的最大可能 分数 。
解题思路
从题目入手,最直观的方法便是根据k的值,将整数数组nums分为左右两个子数组,并依次遍历两个数组,计算出分数的最大值。但是这种方法需要的时间复杂度达到了O(n2),不太符合一道困难题该有的样子。
在草稿纸上写了些码了下字就看不懂的东西后,得到了一个思路
我们需要获得好子数组的最大分数,按照公式,自然是要将子数组内的最小值“最大化”的同时,让子数组的长度最大。我们有可能会想到先获取到原数组内最小值的位置,但经过观察就可以发现,取到最大分数的子数组通常是将最两边的一些“最小值”舍弃了的,因此获取最小值的意义就没有那么大了,因为我们可能还是要遍历完整个数组才能确定最大分数。
那么如果反过来呢?我们首先各获取到k两边子数组的最大值及其位置,然后...等等就算我获取到了最大值,可他取不到啊,比如上面的数组,我获取到了最大元素“4”和“5”以及他们的下标,可实际上计算时左边取的应该是“3”。但是我又注意到,到了“4”之后,左边的数组再往右缩就没有必要了,因为最小值不会改变,而“好子数组”的长度会缩短,最大分数会减小。
这个时候,我又灵光一闪,那我们可以从中间开始向两边推啊emmm,也不现实,如果某靠边的数字特别大...哦再大也是取最小值啊,所以如果在往两边走的过程中,好子数组的分数减小了,那就没必要再继续下去了...吗?如果在走的过程中最小值减小导致整体分数下降,但是之后元素保持不变,只要不变的长度够长,分数也会反超。这就是困难题的含金量吗!
没办法,先试试暴力解法能不能过吧
class Solution:
def maximumScore(self, nums: List[int], k: int) -> int:
left = nums[:k+1]
right = nums[k:]
max_score = 0
left_index = 0
for i in left:
right_idex = k
for j in right:
if left_index != right_idex:
score = min(nums[left_index:(right_idex+1)]) * (right_idex - left_index + 1)
else:
score = nums[left_index]
max_score = max(max_score, score)
right_idex += 1
left_index += 1
return max_score
果然啊,在数据量小的时候还是可以的,数组一大就寄了。
没什么思路了,看了一眼官方题解,发现原来跟我之前想到的从中间(k)向两边推的思路是一样的,它会先以nums[k]作为子数组中的最小值,然后让nums[k]与左右的元素比较,直到元素比nums[k]的值还小(这就说明分数的计算中,“min(num[i:j+1]”的值需要更新,在更新之前肯定是数组越长,分数越高(因此在更新之前的分数也没必要计算了))。然后将i值更新,重复上述操作,并与已有的最大分数进行比较并更新最大值,直到左右指针到达尽头。
原来跟我上面的思路是差不多的,有点可惜,就差一点就能自己解决了。
代码实现
class Solution:
def maximumScore(self, nums: List[int], k: int) -> int:
n = len(nums)
left, right, i = k - 1, k + 1, nums[k]
ans = 0
while True:
while left >= 0 and nums[left] >= i:
left -= 1
while right < n and nums[right] >= i:
right += 1
ans = max(ans, (right - left - 1) * i)
i = max((-1 if left == -1 else nums[left]), (-1 if right == n else nums[right]))
if i == -1:
break
return ans
复杂度分析
时间O(n),空间O(1)