Leetcode 1124:表现良好的最长时间段(超详细的解法!!!)

给你一份工作时间表 hours,上面记录着某一位员工每天的工作小时数。

我们认为当员工一天中的工作小时数大于 8 小时的时候,那么这一天就是「劳累的一天」。

所谓「表现良好的时间段」,意味在这段时间内,「劳累的天数」是严格 大于「不劳累的天数」。

请你返回「表现良好时间段」的最大长度。

示例 1:

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

提示:

  • 1 <= hours.length <= 10000
  • 0 <= hours[i] <= 16

解题思路

首先不难想到暴力法,我们遍历hours>8hour标记为1,然后计算前缀和,计算前缀和的目的是方便我们计算区间内1的个数。例如,

hours : 	9  9  6  0  6  6  9
ht : 	 0  1  1 -1 -1 -1 -1  1
pre_sum:    0  1  2  1  0 -1 -2 -1
               ↑                 ↑
               i                 j

其中pre_sum[j]-pre_sum[i]=-1-1=-2,就表示[i,j)区间内>8的数量减去<=8的数量的结果。那么我们现在只需要建立左右区间的边界ij,然后暴力枚举所有区间,只要区间的差大于0,我们就更新结果。

class Solution:
    def longestWPI(self, hours: List[int]) -> int:
        t = [0] * (len(hours) + 1)
        for i in range(1, len(t)):
            t[i] += t[i-1] + (1 if hours[i-1] > 8 else -1)
            
        res = 0
        for i in range(len(t)):
            for j in range(i, len(t)):
                if t[j] - t[i]:
                    res = max(res, j - i)
        return res

我们观察数据量发现是1e4级别,那么这种O(n^2)的解法就不行了。有什么改进的策略吗?

其实我们现在的问题转化为了找区间和>0的最长区间(前后边界差大于0)。怎么做?实际上你可以这样想,想要pre_sum[j]大于pre_sum[i],那么我们只需找到比pre_sum[j]更小的元素在哪即可。这就可以通过单调栈来实现。关于单调栈的一些例子可以看我的Leetcode 单调栈问题总结(超详细!!!)

但是这里的单调栈使用和之前的使用方法有区别,之前的一些单调栈问题,我们计算的都是下一个和前一个这种问题,这是一个最近的概念,而我们这里希望计算的是最远

所以我们不妨先遍历一遍pre_sum,建立一个严格单调递减栈,这样我们的栈顶元素一定是pre_sum中的最小值。

接着我们再从后向前遍历pre_sum(贪心的策略,我们希望最远),此时如果遍历到的元素大于栈顶元素的话,那么这个区间合法,我们需要记录区间大小,并且我们需要将栈顶元素弹出,然后继续比较当前遍历到的元素是不是大于栈顶元素。这里我们为什么要将元素弹出呢?我们定义变量ij1j2,并且i < j1 < j2,此时如果pre_sum[j2] > pre_sum[i]的话,那么我们的pre_sum[j1]显然就不用再参与比较了,因为不可能比j2-i更大。

class Solution:
    def longestWPI(self, hours: List[int]) -> int:
        t = [0] * (len(hours) + 1)
        for i in range(1, len(t)):
            t[i] += t[i-1] + (1 if hours[i-1] > 8 else -1)
            
        s = list()
        for i, v in enumerate(t):
            if not s or t[s[-1]] > v:
                s.append(i)
        
        res = 0
        for i in range(len(t)-1, -1, -1):
            while s and t[s[-1]] < t[i]:
                res = max(res, i - s[-1])
                s.pop()
        return res

实际上转化后的问题就是Leetcode 560:和为K的数组(最详细的解法!!!)特例。在这个问题中K=1,为什么呢?不应该是K>0都可以吗?是的,确实是这样,但是pre_sum-1一定出现在pre_sum-K(其中K>1)的最前面(因为我们从pre_sum>0的时候开始记录第一个结果),所以使用K=1的话一定最优。

class Solution:
    def longestWPI(self, hours: List[int]) -> int:
        res = pre_sum = 0
        seen = {}
        
        for i, h in enumerate(hours):
            pre_sum += (1 if h > 8 else -1)
            if pre_sum not in seen:
                seen[pre_sum] = i
            if pre_sum > 0:
                res = i + 1
            elif pre_sum - 1 in seen:
                res = max(res, i - seen[pre_sum - 1])
        return res

整体的算法思路非常简单,我们遍历hours,计算前缀和,当前缀和大于0的时候开始记录res,同时记录相同前缀和第一次出现的位置。当前缀和<=0的时候我们只需在记录过的数中查找是不是有pre_sum-1即可。

reference:

https://leetcode.com/problems/longest-well-performing-interval/discuss/335163/O(N)-Without-Hashmap.-Generalized-ProblemandSolution%3A-Find-Longest-Subarray-With-Sum-greater-K.

https://leetcode.com/problems/longest-well-performing-interval/discuss/334565/JavaC%2B%2BPython-O(N)-Solution-Life-needs-996-and-669

https://leetcode.com/problems/longest-well-performing-interval/discuss/334897/ChineseC%2B%2B-1124.-O(n)

我将该问题的其他语言版本添加到了我的GitHub Leetcode

如有问题,希望大家指出!!!

你可能感兴趣的:(Problems,leetcode解题指南)