[路飞]_每天刷leetcode_12(表现良好的最长时间段)

这是我参与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

复制代码

思考线


解题思路

  1. 题目中的数组元素只有两种,分别为大于8和小于等于8,具体的数字在本题中没有意义,所以我们建辉按一下,用1来表示大于8的元素,用 -1来表示小于等于8的元素,标记为arr.

  2. 那么题目就变成了,找到一个子列,子列中的元素数量严格大于-1的元素数量。也就是需要找到一个子列,子列中所有元素的和大于0.

  3. 这个时候我们可以暴力破解,遍历所有子列,找到和大于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.
    复制代码
  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.

leetcode网友解答

猜你喜欢

转载自juejin.im/post/7035998682352877605