给你一份工作时间表 hours
,上面记录着某一位员工每天的工作小时数。
我们认为当员工一天中的工作小时数大于 8
小时的时候,那么这一天就是「劳累的一天」。
所谓「表现良好的时间段」,意味在这段时间内,「劳累的天数」是严格 大于「不劳累的天数」。
请你返回「表现良好时间段」的最大长度。
示例 1:
输入:hours = [9,9,6,0,6,6,9]
输出:3
解释:最长的表现良好时间段是 [9,9,6]。
提示:
1 <= hours.length <= 10000
0 <= hours[i] <= 16
分析:
首先暴力肯定过不了全部,因为复杂度过高。暴力思路:遍历从i=[0,len-1],然后以i为段的左端,在遍历一个j从[i,len-1],作为段的右端,维护一个长短即可。
问题其实有比较抽象的地方,也可以化简下,提问求一个时间段,这个时间段中,「劳累的天数」严格 大于「不劳累的天数」,那么怎么去计算一段时间内,>8的天数与<=8的天数大小呢?这里面数字还可能不一样,所以先预处理,令所有>8都变成1,
所有<=8变成0,
其实通常会想到前缀和,来优化计算暴力法中的一个段[i,j]内的情况。i到j中只要前缀和preSum[j]>preSum[i]是不是就是对的?没错,这样就是符合题意的个答案。题目求一个最长的时间段,那么从后往前遍历,问题就转换为了上一题求最大宽度的问题了。
那么也就需要一个单调栈来协助问题的解决的,同样,需要一个单调递减的栈。
首先要明确我们用的前缀和的差,arr[i]-arr[j].是指 [i+1,j]这个区间内的!这点很重要。
所以,最好的情况,就是开一个前缀和数组,int[] arr = new int[len+1]。从1开始计数。
单调减的栈中,存入0,确保原数组可以包含1,就是-0的情况。
public int longestWPI(int[] hours) {
int len = hours.length;
if(len<2)return 0; // 特判
// 预处理,将>8变成1,否则变成0
int[] arr = new int[len+1];
for(int i = 0 ; i < len ; i++){
if(hours[i]>8){
arr[i+1]=1;
}else
arr[i+1]=-1;
}
// 前缀和
for(int i = 2 ; i <= len ; i++){
arr[i]+=arr[i-1];
}
// 维护一个单调递减栈
Stack<Integer> stack = new Stack<Integer>();
stack.add(0); // 维护下标,首先第一个肯定是在里面的
for(int i = 1 ; i <= len ; i++){
if(arr[i]<arr[stack.peek()]){
stack.add(i);
}
}
// 从后朝前遍历
int res = 0;
for(int i = len ; i > 0 ; i--){
while(!stack.isEmpty() && arr[i]>arr[stack.peek()]){
if(i<=stack.peek()){ // 避免i出现在stack.peek左侧的情况,
stack.pop();
continue;
}// 0 -1 -2 -1
res = Math.max(res, i-stack.pop());
}
}
return res;
}