LC-1124.表现良好的最长时间段

题目描述

leetcode 中等题

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

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

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

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

示例1:

1
2
3
输入:hours = [9,9,6,0,6,6,9]
输出:3
解释:最长的表现良好时间段是 [9,9,6]

提示1:

1
2
1 <= hours.length <= 104
0 <= hours[i] <= 16

贪心 + 单调栈

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

那么可以将大于 8 小时的天数转化成 $1$ ,否则转换成 $-1$ ,此时 表现良好的时间段 指的就是某段和为正数的子数组,满足条件的最长子数组的长度即为答案。

需要满足子数组和为正数,那么容易想到构造一个原数组的前缀和数组,其中 $preSum[i]$ 表示原数组 $[0…i - 1]$ 的元素和。此时可以通过遍历前缀和数组,对于 $preSum[i]$ 找到一个左边最远的下标 $left$ ,使得满足 $preSum[i] - preSum[left] > 0$ ,此时 $max(i - left)$ 为答案。

对于下标 $i$ 找到左边最远的满足 $preSum[left] < preSum[i]$ 的 $left$ ,一种方法是通过单调栈来实现。

我们维护一个栈,从栈底到栈顶元素单调递减。

考虑顺序遍历,若 $preSum[i] > stk.peek()$,则弹出栈顶最小元素。

  • 如果 $preSum[i] > preSum[i + 1]$ ,当前的 $i$ 可能将后续所需要的栈元素弹出,且以后续下标为右端点的区间可能更长,导致结果不正确;

考虑逆序遍历,若 $preSum[i] > stk.peek()$,则弹出栈顶最小元素。

  • 如果 $preSum[i - 1] < preSum[i]$ ,虽然当前 $i$ 弹出的元素也会影响到 $i - 1$ ,但是因为如果以 $preSum[i - 1]$ 为右端点能取到一个满足题意的区间时,$preSum[i]$ 也必然能够取到,且更长,所以没必要考虑 $i - 1$ 。
  • 如果 $preSum[i - 1] > preSum[i]$,则当前 $i$ 所弹出的元素都是 $i - 1$ 所要弹出的,所以不会影响答案的正确性。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Solution {
public int longestWPI(int[] hours) {
hours = Arrays.stream(hours).map(i -> i > 8 ? 1 : - 1).toArray();
int n = hours.length;
int[] preSum = new int[n + 1];
for(int i = 1; i <= n; i++){
preSum[i] = preSum[i - 1] + hours[i - 1];
}
Deque<Integer> monoStack = new ArrayDeque<>();
for(int i = 0; i <= n; i++){ // push 0 避免后面特判 preSum[i] 为正数
if(monoStack.isEmpty() || preSum[monoStack.peek()] > preSum[i]){
monoStack.push(i);
}
}
int ans = 0;
for(int i = n; i >= 1; i--){
int left = -1;
while(!monoStack.isEmpty() && preSum[monoStack.peek()] < preSum[i]){
left = monoStack.pop();
}
ans = left == -1 ? ans : Math.max(ans, i - left);
}
return ans;
}
}

贪心 + 哈希表

我们也可以不需要单调栈来实现找到左边最远的满足 $preSum[left] < preSum[i]$ 的 $left$ 。

这是因为前缀和数组满足连续性,也就是如果某个数 $i$ 存在,则 $i - 1$ 也必然存在。

所以对于某个 $preSum[i]$ ,此时左边最远的小于 $preSum[i]$ 的值,就必然是 $preSum[i] - 1$ 。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class Solution {
public int longestWPI(int[] hours) {
hours = Arrays.stream(hours).map(i -> i > 8 ? 1 : - 1).toArray();
int n = hours.length;
int[] preSum = new int[n + 1];
for(int i = 1; i <= n; i++){
preSum[i] = preSum[i - 1] + hours[i - 1];
}
Map<Integer, Integer> indexMap = new HashMap<>();
int ans = 0;
for(int i = 1; i <= n; i++){
if(preSum[i] > 0){
ans = Math.max(ans, i);
continue;
}
indexMap.putIfAbsent(preSum[i], i);
if(indexMap.get(preSum[i] - 1) != null){
ans = Math.max(ans, i - indexMap.get(preSum[i] - 1));
}
}
return ans;
}
}

LC-1124.表现良好的最长时间段
https://wecgwm.github.io/2023/02/14/LC-1124-表现良好的最长时间段/
作者
yichen
发布于
2023年2月14日
许可协议