一个动态规划问题|豆包MarsCode AI刷题

107 阅读5分钟

问题

小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)和不劳累天数(-1)。
  2. 前缀和:我们使用一个字典 prefix_sums 来记录每个前缀和最早出现的位置。
  3. 滑动窗口:我们遍历转换后的数组,计算当前前缀和 current_sum。如果存在一个更早的前缀和比当前前缀和小1,说明从那个位置到当前位置的子数组和为正数,即劳累天数多于不劳累天数。
  4. 记录位置:我们只在当前前缀和没有被记录过时才记录其位置,以避免不必要的重复计算。
  5. 特殊处理:如果最终的 current_sum 大于 0,说明整个数组就是一个「表现良好时间段」,因此直接返回数组的长度。