问题
小M在分析员工的工作时间。他认为当某一天的工作小时数超过 8 小时,这一天就是「劳累的一天」。他想找出最长的一段连续工作日,在这段时间内,「劳累的天数」严格大于「不劳累的天数」。你需要返回这个「表现良好时间段」的最大长度。--题目来源自:豆包487
我们来看一下测试用例:
样例1
输入:`hours = [9,9,6,0,6,6,9]`
输出:`3`
样例2
输入:`hours = [6,6,6,8]`
输出:`0`
样例3
输入:`hours = [10,10,10,0,0,9]`
输出:`6`
问题分析
要解决这个问题,我们可以使用滑动窗口的方法来寻找满足条件的最长子数组。具体来说,我们可以将“劳累的一天”视为 +1,“不劳累的一天”视为 -1,然后寻找一个子数组,使得该子数组的和为正数,并且长度尽可能长。
代码实现
def longest_good_period(hours):
"""
找出最长的一段连续工作日,在这段时间内,「劳累的天数」严格大于「不劳累的天数」。
:param hours: list[int] 每天的工作小时数列表
:return: int 最长「表现良好时间段」的长度
"""
# 转换数据,劳累的一天为+1,不劳累的一天为-1
transformed_hours = [1 if h > 8 else -1 for h in hours]
max_length = 0
current_sum = 0
prefix_sums = {0: -1} # 记录每个前缀和最早出现的位置
for i, hour in enumerate(transformed_hours):
current_sum += hour
# 如果当前前缀和已经存在,说明从上次出现该前缀和的位置到当前位置的子数组和为0,
# 这意味着这个子数组中劳累天数和不劳累天数相等,因此可以尝试更新最大长度
if current_sum in prefix_sums:
start_index = prefix_sums[current_sum]
max_length = max(max_length, i - start_index)
else:
# 记录当前前缀和出现的位置
prefix_sums[current_sum] = i
# 我们还需要检查是否存在一个更早的前缀和比当前前缀和小1的情况,
# 因为这表明从那个位置到当前位置的子数组和为正数,即劳累天数多于不劳累天数
if current_sum - 1 in prefix_sums:
start_index = prefix_sums[current_sum - 1]
max_length = max(max_length, i - start_index)
return max_length
这段代码首先将每天的工作小时数转换成劳累或不劳累的标记(+1 或 -1),然后通过遍历这些标记来计算前缀和,并利用字典记录每个前缀和第一次出现的位置。这样可以快速找到满足条件的子数组,从而求得最长的「表现良好时间段」,似乎这个代码实现已经可以初步实现我们的需求,但是提交后我们发现在面对
hours=[9, 9, 6, 0, 6, 6, 9]) == 3)这个结果时似乎并不正确
def longest_good_period(hours):
"""
找出最长的一段连续工作日,在这段时间内,「劳累的天数」严格大于「不劳累的天数」。
:param hours: list[int] 每天的工作小时数列表
:return: int 最长「表现良好时间段」的长度
"""
# 转换数据,劳累的一天为+1,不劳累的一天为-1
transformed_hours = [1 if h > 8 else -1 for h in hours]
max_length = 0
current_sum = 0
prefix_sums = {0: -1} # 记录每个前缀和最早出现的位置
for i, hour in enumerate(transformed_hours):
current_sum += hour
# 如果存在一个更早的前缀和比当前前缀和小1的情况,
# 这表明从那个位置到当前位置的子数组和为正数,即劳累天数多于不劳累天数
if current_sum - 1 in prefix_sums:
start_index = prefix_sums[current_sum - 1]
max_length = max(max_length, i - start_index)
# 记录当前前缀和出现的位置
if current_sum not in prefix_sums:
prefix_sums[current_sum] = i
return max_length
在这个版本中,我们只在需要的时候更新 max_length,即当我们发现一个更早的前缀和比当前前缀和小1的时候。同时,我们确保每个前缀和只记录一次其出现的位置,以避免不必要的重复计算。
不过这个问题仍然不正确,在面对这个solution(hours=[10, 10, 10, 0, 0, 9]) == 6时并不正确
def longest_good_period(hours):
"""
找出最长的一段连续工作日,在这段时间内,「劳累的天数」严格大于「不劳累的天数」。
:param hours: list[int] 每天的工作小时数列表
:return: int 最长「表现良好时间段」的长度
"""
# 转换数据,劳累的一天为+1,不劳累的一天为-1
transformed_hours = [1 if h > 8 else -1 for h in hours]
max_length = 0
current_sum = 0
prefix_sums = {0: -1} # 记录每个前缀和最早出现的位置
for i, hour in enumerate(transformed_hours):
current_sum += hour
# 如果当前前缀和比某个更早的前缀和大1,说明从那个位置到当前位置的子数组和为正数
if current_sum - 1 in prefix_sums:
start_index = prefix_sums[current_sum - 1]
max_length = max(max_length, i - start_index)
# 记录当前前缀和出现的位置
if current_sum not in prefix_sums:
prefix_sums[current_sum] = i
# 特殊处理整个数组的情况
if current_sum > 0:
max_length = max(max_length, len(hours))
return max_length
解释
- 转换数据:我们将每天的工作小时数转换为劳累天数(+1)和不劳累天数(-1)。
- 前缀和:我们使用一个字典
prefix_sums来记录每个前缀和最早出现的位置。 - 滑动窗口:我们遍历转换后的数组,计算当前前缀和
current_sum。如果存在一个更早的前缀和比当前前缀和小1,说明从那个位置到当前位置的子数组和为正数,即劳累天数多于不劳累天数。 - 记录位置:我们只在当前前缀和没有被记录过时才记录其位置,以避免不必要的重复计算。
- 特殊处理:如果最终的
current_sum大于 0,说明整个数组就是一个「表现良好时间段」,因此直接返回数组的长度。