题目描述
给你一份工作时间表 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 合要求,我们就看前两个,否则继续找,知道找到合理的 j 且 i - 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 到它的和