[路飞]1124. 表现良好的最长时间段

164 阅读2分钟

题目描述

给你一份工作时间表 hours,上面记录着某一位员工每天的工作小时数。

我们认为当员工一天中的工作小时数大于 8 小时的时候,那么这一天就是「劳累的一天」。

所谓「表现良好的时间段」,意味在这段时间内,「劳累的天数」是严格 大于「不劳累的天数」。

请你返回「表现良好时间段」的最大长度。

分析

输入:Array,表示员工每天的工作小时数
输出:Array,最长的“良好时间段”天数

解题思路

首先,我们为了统计方便,先把愿数组的小时数数组,map 为是否加班数组,加班 map 为 1,不加为 -1。

那么我们如果要把 [9,9,6,0,6,6,9] map,会得到 [1, 1, -1, -1, -1, -1, 1]

由此题目转化为了求和大于0的最长区间。然后为了方便计算得到数组的某个区间的和,我们再生成一个数组,对于每个 index i 所在的位置的值是 0 - i 所有元素的和(注意我们现在一开始加一个元素 0),

由于此得到数组 prefixSum [0, 1, 2, 1, 0, -1, -2, 1], 这就是传说中的前缀和,是我们这题最重要的部分,

如果我们要求第 i - j 区间的良好天数,我们只需要令 prefixSum[j] - prefixSum[i],可得结果。

然后我们看区间的 i 怎么找:从头开始,对于任意的 i, j,如果 i1 < i2, prefixSum[i1] < prefixSum[i2],那么我们的 i 一定是 i1,因为我们要找最长区间,因此记录下 index,得到 [0, 5, 6]

最后是 j:首先,如果 i === 6,对于一个不合要求的 j,则对于 0, 5 的情况也一定不行。因此我们从后向前遍历找 j

如果对于 6 合要求,我们就看前两个,否则继续找,知道找到合理的 ji - j 最长。

代码

/**
       * @param {number[]} hours
       * @return {number}
       */
      var longestWPI = function (hours) {
        //  为什么要把 hours map 成 1 - 1 数组?
        //  因为题目要求我们返回的是最长的连续加班时间,如果每个 item 都是加班或不加班两种情况
        //  所以我们转化成 -1 1 数组,方便统计
        const hoursMap = hours.map((item) => (item > 8 ? 1 : -1))

        //  为什么要生成前缀数组?
        //  因为要找长度最长且和为 0 的子列,通过前缀数组可以通过 (arr[j] - arr[i]) 拿到 arr[i], arr[j] 的和
        //  需要做的是 prefixSum[j + 1] - prefixSum[i]
        //  需要注意的是,这里加了一个元素,方便累加
        const prefixSum = hoursMap.reduce(
          (prev, cur) => {
            prev.push(prev[prev.length - 1] + cur)
            return prev
          },
          [0]
        )

        //  为什么要从头找一个单调递减的数组
        //  对于 i < i1,如果 prefixSum[i] < prefixSum[i1] 那么 i1 一定被舍弃,原因是从 i 开始更长
        //  所以只有如果第一个 i 位置被舍弃,下一个位置的 i1 对应的 prefixSum[i1] 一定会小于 prefixSum[i]
        //  所以只有可能是递减数组中的某一项作为起始位置的 i

        //  最后,寻找 i, j 使得从 i 到 j 是最长合法序列的方法
        //  从后向前找 j,如果 prefixSum[j] > prefixSum[decrementalStack[-1]],那么 (decrementalStack[-1], j) 合法
        //  再找下一个 i,看 (decrementalStack[-2], j) 是否合法,如果合法则记录下
        //  用此方法,则可找本体要求的最长序列
        const decrementalStack = [0]
        prefixSum.forEach((item, index) => {
          if (item < prefixSum[decrementalStack[decrementalStack.length - 1]])
            decrementalStack.push(index)
        })

        let res = 0
        for (let i = prefixSum.length - 1; i > 0; i--) {
          while (
            decrementalStack.length &&
            prefixSum[i] >
              prefixSum[decrementalStack[decrementalStack.length - 1]]
          ) {
            res = Math.max(
              res,
              i - decrementalStack[decrementalStack.length - 1]
            )
            decrementalStack.pop()
          }
        }

        return res
      }

复杂度

时间:O(N),遍历几遍字符串
空间:O(N),前缀和数组需要存每个位置从 0 index 到它的和