题目描述
给你一份工作时间表 hours,上面记录着某一位员工每天的工作小时数。
我们认为当员工一天中的工作小时数大于 8 小时的时候,那么这一天就是「劳累的一天」。
所谓「表现良好的时间段」,意味在这段时间内,「劳累的天数」是严格 大于「不劳累的天数」。
请你返回「表现良好时间段」的最大长度。
示例 1:
输入:hours = [9,9,6,0,6,6,9]
输出:3
解释:最长的表现良好时间段是 [9,9,6]。
示例 2:
输入:hours = [6,6,6]
输出:0
提示:
- 1 <= hours.length <= 104
- 0 <= hours[i] <= 16
思路
可以使用前缀和的思想来解题,应该很多人都会想到,不过本题还需要一些额外的小技巧。
首先处理原数组,将大于8的映射成1,小于等于8的映射成-1,这样原题就转换成了求最长的一段使得这段的和大于0。既然要求连续一段的和,很自然而然想到了前缀和:定义一个长度为length+1的数组sum[],初始化sum[0] = 0,然后赋值,sum[i+1] = sum[i] + (hours[i]>8?1:-1)。预处理得这个前缀和数组后,我们就可以在O(1)的时间复杂度内获取任意一段和:sum[j] - sum[i]就是原始数组在[i, j)这一段的和。
所以,接下来原题就转换成了,求解尽可能大的right-left,满足sum[right]>sum[left](right >= left)。这里先给结论,最佳的left肯定在sum[]的从0开始的单调递减子数组上。
为什么呢?可以假设这个单调递减的数组下标为0,5,10,15,如果存在在这个子数组以外的下标,例如7,是最佳的left位置,那么由于7的左边明显还存在sum[5]<sum[7],而跟7匹配的最佳right无论在哪个位置[5, right]的长度明显比[7,right]更长,这样就矛盾了。那如果sum[5]>sum[7]呢?这样就跟假设矛盾了,因为这样的话,7就应该在单调递减子数组中。
有了这个结论,我们就可以构造一个单调栈,单调栈中存储这个子数组的下标,然后从后往前遍历sum作为right,如果 sum[j] > sum[stack.peek()] 那么 stack.peek() 和 j 就是一组符合题意的备选left和right了。
注意到代码中用到了stack.pop(),为什么不是新进入for循环去查询原单调栈,而可以pop出元素呢?相信你已经有了答案。
Java版本代码
class Solution {
public int longestWPI(int[] hours) {
int ans = 0;
int[] sum = new int[hours.length+1];
sum[0] = 0;
Stack<Integer> stack = new Stack<>();
stack.push(0);
for (int i = 0; i < hours.length; i++) {
sum[i+1] = sum[i] + (hours[i]>8?1:-1);
if (sum[i+1] < sum[stack.peek()]) {
stack.push(i+1);
}
}
for (int j = hours.length; j >= 1; j--) {
while (!stack.isEmpty() && sum[j] > sum[stack.peek()]) {
ans = Integer.max(ans, j - stack.pop());
}
}
return ans;
}
}
开启掘金成长之旅!这是我参与「掘金日新计划 · 2 月更文挑战」的第 13 天,点击查看活动详情