算法随笔_50: 表现良好的最长时间段
上一篇:算法随笔_49: 有效的括号-CSDN博客
=====
题目描述如下:
给你一份工作时间表 hours
,上面记录着某一位员工每天的工作小时数。
我们认为当员工一天中的工作小时数大于 8
小时的时候,那么这一天就是「劳累的一天」。
所谓「表现良好的时间段」,意味在这段时间内,「劳累的天数」是严格 大于「不劳累的天数」。
请你返回「表现良好时间段」的最大长度。
示例 1:
输入:hours = [9,9,6,0,6,6,9] 输出:3 解释:最长的表现良好时间段是 [9,9,6]。
示例 2:
输入:hours = [6,6,6] 输出:0
=====
算法思路:
根据题目描述,我们发现,判断“劳累的天数”是严格大于“不劳累的天数”,可以转化为一个求和问题。我们把大于8的元素置为1,小于等于8的元素置为-1,那么某个区间的元素和大于等于1时,就表示了“劳累的天数”严格大于“不劳累的天数”。
那么题目的要求返回表现良好时间段的最大长度,就变为了求元素和大于0的最长区间。
对于计算区间元素和,有一个常用的方法: 前缀和。
我们设数组nums,pre_sum。
pre_sum[i]表示nums的[0, i]区间的元素和。
pre_sum[j]表示nums的[0, j]区间的元素和。
i < j,因此[i+1, j]区间的元素和就是这两个前缀和的差,即,pre_sum[j] - pre_sum[i]。
介绍了计算区间元素和的方法,我们再来看一下如何寻找最长的区间。
我们先考虑以某个索引 j 为某个区间的右端点,那么如何求出这个区间的最左端点 i 呢?显然最左端点应该出现在从左往右数的第一个使pre_sum[j] - pre_sum[i]>0的索引处。但大于0的值有很多,我们选取哪个值来计算pre_sum[i]呢?我们选择1即可。假如pre_sum[j]=-5,我们选第一次出现的pre_sum[i]=-6,因为如果选择pre_sum[i]=-7,-8等。会导致索引i更靠近右侧,肯定不是最左端点。因此我们找到第一个前缀和为pre_sum[j]-1处的索引即可。
我们枚举所有索引,并找到它们各自的最长区间,最后取它们的最大值即为最终答案。
基本的算法如下:
1. 我们设res为最大区间长度,初始值为0。从左往右枚举数组hours,同时计算当前索引的前缀和pre_sum。如果hours[i]>8,pre_sum加1,否则减1。
2. 把第一次出现的pre_sum存入字典sum2ind中,值为索引i,即,sum2ind[pre_sum]=i。
3. 判断pre_sum-1是否存在于sum2ind,如果存在,用当前索引i减去sum2ind[pre_sum -1]。这就是以当前索引 i 为右端点的最长区间长度seg_len。同时更新res=max(res, seg_len) 。
对于pre_sum大于0的情况,代码实现时有两点需要注意:
1. 我们把前缀和为0的索引设置在-1处,即sum2ind[0]=-1。以方便后面的计算。
2. 当前缀和大于0时,我们统一把pre_sum置为1。这并不影响最终的结果,因为当前缀和大于0时,它的最左端点就是sum2ind[0]=-1处。我们把它置为1,也是为了方便计算前面算法提到的步骤3。当所有pre_sum > 0 时,pre_sum -1都能定位到sum2ind[0]=-1处。
此算法的时间复杂度为O(n) 。下面是代码实现:
class Solution(object):
def longestWPI(self, hours):
"""
:type hours: List[int]
:rtype: int
"""
sum2ind = {0: -1}
pre_sum = 0
res = 0
for i, hour in enumerate(hours):
pre_sum += 1 if hour > 8 else -1
k = 1 if pre_sum > 0 else pre_sum
if k - 1 in sum2ind:
res = max(res, i - sum2ind[k - 1])
if k not in sum2ind:
sum2ind[k] = i
return res
关键词: 前缀和