(高难)前缀和+单调栈求解表现良好的最长时间段

158 阅读4分钟

正题

给你一份工作时间表 hours,上面记录着某一位员工每天的工作小时数。我们认为当员工一天中的工作小时数大于 8 小时的时候,那么这一天就是「劳累的一天」。所谓「表现良好的时间段」,意味在这段时间内,「劳累的天数」是严格 大于「不劳累的天数」。请你返回「表现良好时间段」的最大长度。

示例 1:

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

开始以为是一个比较简单的动态规划问题,后来才发现是我把内容想的太过简单!

首先了解一下题意, > 8 小时是劳累的一天,小于8小时为不劳累,实际上是求劳累天数 > 不劳累天数的最长时间段。

示例 [9,9,6,0,6,6,9],前三个元素 9,9,6 位第一个表现良好时间段,理由很简单:3天内有2天>8小时,那么长度为3.第二个良好为最后两个元素 6,9 理由同上,长度为2,所以最大长度为3

先考虑动态规划方案解决, dpList 可以求得前面几天的最长时间段依次是多少,但是到了末尾2个元素的时候,就很难判断,因为倒数第二个6由于最后一个元素是9,也可被列为表现良好时间段内,那么动态规划也要考虑后面一个甚至多个元素,无疑对解题的复杂度提升了好几个档次,经过几次尝试之后果断放弃。。

可以看到表现良好的时间段并不在乎数组的数字大小具体是多少,只需要关注数字是否 >8。那么我们可以将所有的数据分为2类,一类是大于8的时间,一类是小于8的时间。要求连续>8的天数要小于8的天数,那么可以将大于8的天数定义为1,小于8的天数定义为-1,试想,如果连续相加>0,是不是可以认为是良好时间段?由此,可以得到新的数组:

[1,1,-1,-1,-1,-1,1]

进行遍历相加得到前缀和:

[0,1,2,1,0,-1,-2,-1]

1.gif

那么前缀和的意义是什么呢?

题目要求返回表现良好时间段的最大长度,即求最长的一段中,得分 1 的个数大于得分 -1 的个数,也就是求 presum 数组中最长的一段子数组,其和大于 0,那么也就是找出前缀和数组 presum 中两个索引 i 和 j,使 j - i 最大,且保证 presum[j] - presum[i] 大于 0.因此只需要求得 presum 数组的最长上坡即可!那么求的最长上坡,我们可以先做 presum 的单调递减栈。

2.gif

由上图可以得到单减栈的下标顺序是 [0,5,6],其元素表示为 [0, -1 , -2],那么单减栈的意义在于什么呢?

到此表示 ,以 0 为起点,到第五个元素(6)前缀和都是大于0的,以 第五个元素 -1 为起点,到 第六个元素 (9) ,其都是大于 0 的

然后我们从后往前遍历 presum 数组,与栈顶索引指向元素比较,如果相减结果大于 0,则一直出栈,直到不大于 0 为止,然后更新当前最大宽度。借用一张动图。

5baaaa25c9b0158663cd3757f59e28c516ed6f867a3acc5a73abb509cc8a422f-1124-1.gif

完整代码:

/**
 * @param {number[]} hours
 * @return {number}
 */
 // > 8 
 // 
var longestWPI = function(hours) {
   // 前缀和
    let preSum = new Array(hours.length+1).fill(0)
    for (let i = 0; i < hours.length; i++) {
        if (hours[i] > 8) preSum[i+1] = preSum[i] + 1
        else preSum[i+1] = preSum[i] - 1
    }

    // 单减栈
    let stack = []
    stack.push(0)
    for (let i = 1; i < preSum.length; i++){
        if (preSum[stack[stack.length-1]] > preSum[i]) stack.push(i)
    }

    // 从右到左求最大跨度
    let max = 0
    for (let i = preSum.length-1; i > max; i--){
        while(stack.length > 0 && preSum[stack[stack.length-1]] < preSum[i]){
            max = Math.max(max, i - stack.pop() )
        }
    }
    return max

};

这题的解法难度很大,算是中等里面很难的题了,对于理解上需要有一定的要求,之后会持续关注此类问题的类似题型和解法,加深对该算法的理解,目前对算法理解不够,表达不尽意,只做笔记,温习提示。