这是我参与11月更文挑战的第20天,活动详情查看:2021最后一次更文挑战」
表现良好的最长时间段
LeetCode原题传送门 1124. 表现良好的最长时间段
题目
给你一份工作时间表 hours,上面记录着某一位员工每天的工作小时数。
我们认为当员工一天中的工作小时数大于 8 小时的时候,那么这一天就是「劳累的一天」。
所谓「表现良好的时间段」,意味在这段时间内,「劳累的天数」是严格 大于「不劳累的天数」。
请你返回「表现良好时间段」的最大长度。
We are given hours, a list of the number of hours worked per day for a given employee.
A day is considered to be a tiring day if and only if the number of hours worked is (strictly) greater than 8.
A well-performing interval is an interval of days for which the number of tiring days is strictly larger than the number of non-tiring days.
Return the length of the longest well-performing interval.
Example:
Input: hours = [9,9,6,0,6,6,9]
Output: 3
Explanation: The longest well-performing interval is [9,9,6].
Input: hours = [6,6,6]
Output: 0
复制代码
思考线
解题思路
-
题目中的数组元素只有两种,分别为大于8和小于等于8,具体的数字在本题中没有意义,所以我们建辉按一下,用1来表示大于8的元素,用 -1来表示小于等于8的元素,标记为arr.
-
那么题目就变成了,找到一个子列,子列中的元素数量严格大于-1的元素数量。也就是需要找到一个子列,子列中所有元素的和大于0.
-
这个时候我们可以暴力破解,遍历所有子列,找到和大于0且长度最大的子列即可。也可以运用前缀和数组来优化这一步。
const arr = [1, 1, -1, -1, -1, -1, 1] const prefixSum = []; let cur_sum = 0; arr.forEach( val => { prefixSum.push(cur_sum); cur_sum +=val; }); prefixSum.push(cur_sum); print(prefixSum) // [0, 1, 2, 1, 0, -1, -2, -1] 注意这里比arr多了一个元素 // 这时就能很容易找到每一个子列和了,比如我想要arr[2]+arr[3]+...+arr[5]的和直接用prefixSum[6] - prefixSum[2]即可,得到结果是-4. 复制代码
-
将任意一个子列表示为 (i, j) ,i和j分别是prefixSum数组中某个元素的下标. 我们可以直接遍历每一个(i,j),即可得到 答案,或者我们可以做一下优化。
// 直接遍历版 let res = 0; for(let i = 0; i < prefixSum.length; i ++) { for(let j = i +1; j < prefixSum.length; j ++) { if(prefixSum[i] < prefixSum[j]) { res = Math.max(j-i, res); } } } return res; // 在遍历内层循环j的过程中, 给任意一个i < j1 < j2, 如果prefixSum[i] < prefixSum[j2], 那么(i,j1)一定不会是答案. // 优化版 1 let res = 0; for(let i = 0; i < prefixSum.length; i ++) { for(let j = prefixSum.length -1; j > i; j --) { if(prefixSum[i] < prefixSum[j]) { res = Math.max(j-i, res); break; } } } return res; 复制代码
我们再思考:在遍历外层循环i的过程中, 在对于任意的一个i < i1 < j, 如果prefixSum[i1] >= prefixSum[i],那么(i1, j)一定不会是答案. 这时我们需要从头遍历一遍prefixSum, 找到一个严格单调递减的数组(strictly monotonic descresing stack).
// 优化版 2 part 1 smdStack = []; prefixSum.forEach( (item, i) => { if(smdStack.length === 0 || prefixSum[smdStack[smdStack.length -1]] > item){ smdStack.push(i) } }); 复制代码
反向遍历smdStack,对于一个 j : 如果它满足prefixSum[j] < prefixSum[smdStack[last]], 因为是单调递减的,所以stk中的其他元素都不会再小于prefixSum[j] , 所以j就可以直接被排除掉.
如果对于一个j 如果它满足prefixSum[j] > prefixSum[stk[-1]], 那么(stk[-1], j)就是候选项.同时smdStack之前的元素是不是候选元素还需要继续判断其与prefixSum[j]的关系。 于是得到下面代码
// 优化版 2 part 2 let res = 0; for(let j = prefixSum.length -1; j > 0; j --) { while(stk.length && prefixSum[j] > prefixSum[stk[stk.length -1]]) { res = Math.max(res, j - stk.pop()); } } return res; 复制代码
最后终极优化完的代码如下:
/**
* @param {number[]} hours
* @return {number}
*/
var longestWPI = function (hours) {
const arr = hours.map(item => item > 8 ? 1 : -1);
const prefixSum = [];
let cur_sum = 0;
arr.forEach( val => {
prefixSum.push(cur_sum);
cur_sum +=val;
});
prefixSum.push(cur_sum);
stk = [];
prefixSum.forEach( (item, i) => {
if(stk.length === 0 || prefixSum[stk[stk.length -1]] > item){
stk.push(i)
}
});
let res = 0;
for(let j = prefixSum.length -1; j > 0; j --) {
while(stk.length && prefixSum[j] > prefixSum[stk[stk.length -1]]) {
res = Math.max(res, j - stk.pop());
}
}
return res;
};
复制代码
参考资料
O(N) Without Hashmap. Generalized Problem&Solution: Find Longest Subarray With Sum >= K.