题目
(1)给你一份工作时间表 hours,上面记录着某一位员工每天的工作小时数。
(2)我们认为当员工一天中的工作小时数大于 8 小时的时候,那么这一天就是「劳累的一天」。
(3)所谓「表现良好的时间段」,意味在这段时间内,「劳累的天数」是严格 大于「不劳累的天数」。
(4)请你返回「表现良好时间段」的最大长度。
(5)示例如下:
输入:hours = [9,9,6,0,6,6,9]
输出:3
解释:最长的表现良好时间段是 [9,9,6]。
解决思路
- 题意理解:题意求表现良好的最长时间段即在原数组O中找到最长的一段,且该段的和大于0。具体方法是在前缀和数组中,在某一个值 a 的前面找到比 a 小的值 b,使得a - b > 0,且从 a 到 b 的区间最长。
- 第一步:遍历原数组O,将原数组O转换为由1和-1构成的数组A(当该天表现良好时为1,表现不良好时为-1)。再将数组A转换成前缀和数组B(前缀和数组的作用:能快速计算出数组A的某个区间和,如数组A中从位置 i 到 j 的区间和等于B[j] - B[i])。
- 第二步:遍历前缀和数B,查看前缀和数组中的每个值cnt在之前是否出现过。若没出现过,则设置cnt第一次出现的位置。
- 第三步:如果cnt在之前没有出现过,则计算cnt第一次出现时,以cnt作为结尾且表现良好的区间的长度。
- 第四步:实时计算以 前缀和数组中的每个值作为结尾时,表现良好的区间的长度,并更新最长的长度。
- 上述步骤如下图所示:
代码
- C++代码
# include <stdio.h>
# include <vector>
# include <unordered_map>
# include <algorithm>
using namespace std;
class Solution {
public:
int longestWPI(vector<int>& hours) {
unordered_map<int, int> idx; // 使用哈希表记录前缀和数组中每种值第一次出现的位置。
unordered_map<int, int> f; // 记录前缀和数组中,每个值n第一次出现时,以n作为结尾且表现良好的区间的长度。
idx[0] = -1; // 前缀和数组的第一个元素为0,并将其下标为设置为-1。
f[0] = 0; // 初始化以前缀和数组的第一个元素0作为结尾,且表现良好的区间的长度为0
int cnt = 0; // 用于计算前缀和数组中每个位置的值。
int answer = 0;
for (int i = 0; i < hours.size(); i++) {
// 第一步:计算前缀和数组中的每个值。
if (hours[i] > 8) {
cnt += 1;
} else {
cnt -= 1;
}
// 第二步:查看前缀和数组中cnt值是否出现过。若没出现过,则设置cnt第一次出现的位置,
// 并计算cnt第一次出现时,以cnt作为结尾且表现良好的区间长度的值f[cnt]
if (idx.find(cnt) == idx.end()) {
// 如果在idx中没有找到cnt对应的值,就设置cnt第一次出现的位置。
idx[cnt] = i;
// 第三步:计算cnt第一次出现时,以cnt作为结尾且表现良好的区间的长度。
// 如果在cnt前面没有找到cnt-1的值出现的位置,则以cnt作为结尾且表现良好的区间的长度设置为0
// 注:本题中前缀和数组是连续的,且相邻两个值相差为1,因而只需要在cnt的前面寻找比cnt小1的数。
if (idx.find(cnt - 1) == idx.end()) { // cnt前面没有比cnt小1的值,如图中没有比-2小1的值,因此以-2作为结尾且表现良好的区间的长度为0
f[cnt] = 0;
} else {
// 在cnt前面出现了比cnt小1的值,则使用递推公式求以cnt作为结尾,且表现良好的区间的长度。
// 递推公式:f(n) = f(n-1) + p(n) - p(n-1),其中p(n)表示值n此刻出现的位置,p(n-1)表示值n-1第一次出现的位置。
f[cnt] = f[cnt - 1] + (i - idx[cnt - 1]);
}
}
// 第四步:实时计算以 前缀和数组中的每个值作为结尾时,表现良好的区间的长度,并更新最长的长度。
// 如果cnt在之前出现过,且比cnt小1的值也出现过,则可使用递推公式求此刻以cnt作为结尾且表现良好的区间的长度,并更新最长区间的长度。
// 如果比cnt小1的值在前面没有出现过,则此刻以cnt作为结尾且表现良好的区间长度为0,则不用更新最长区间的长度。
// 注意,不能使用answer = max(answer, f[cnt]); 因为f[cnt]表示cnt第一次出现时表现良好的区间的长度。而在前缀和数组中,cnt的值等于
// 同一个值的情况可能在多个位置出现,因而在求表现良好的最长区间时,要时实计算以cnt作为结尾且表现良好的区间的长度。
// 如图中1出现在了多个位置,而f[1]表示值1第一次出现时表现良好的区间长度。因此要重新计算f[cnt - 1] + i - ind[cnt - 1]的大小。
if (idx.find(cnt - 1) != idx.end()) {
int length = f[cnt - 1] + i - idx[cnt - 1]; // 实时计算此刻以cnt最为结尾时表现良好的区间的长度。
answer = max(answer, length);
}
}
return answer;
}
};
int main() {
vector<int> hours = {9,9,6,0,6,6,9};
Solution *solution = new Solution();
printf("%d\n", solution->longestWPI(hours));
return 0;
}
- Python代码
# -*- coding: utf-8 -*-
from typing import List
class Solution:
def __init__(self):
pass
def longestWPI(self, hours: List[int]) -> int:
idx = dict() # 记录前缀和数组中每种值第一次出现的位置。
fn = dict() # 记录前缀和数组中,每个值n第一次出现时,以n作为结尾且表现良好的区间的长度。
idx[0] = -1 # 前缀和数组的第一个元素为0,并将其下标为设置为-1。
fn[0] = 0 # 初始化以前缀和数组的第一个元素0作为结尾,且表现良好的区间的长度为0
sum = 0 # 用于计算前缀和数组中每个位置的值。
answer = 0
hours_len = len(hours)
score = [0] * (hours_len + 1) # 前缀和数组,前缀和数组的第一个元素要设置为0,因而数组的长度比hours的长度大1.
# 第一步:计算前缀和数组中的每个值。
for i in range(hours_len):
sum += 1 if hours[i] > 8 else -1
score[i + 1] = sum # 因为前缀和数组的第一个元素必须为0,因此从第二个位置开始存放计算出来的值。
# 第二步:遍历前缀和数组,查看前缀和数组中cnt值是否出现过。若没出现过则设置cnt第一次出现的位置。
for i in range(hours_len):
cnt = score[i + 1]
# 若cnt没出现过,则设置cnt第一次出现的位置,并计算cnt第一次出现时,以cnt作为结尾且表现良好的区间长度的值f[cnt]
if cnt not in idx.keys():
idx[cnt] = i # 如果在idx中没有找到cnt对应的值,就设置cnt第一次出现的位置。
# 第三步:计算cnt第一次出现时,以cnt作为结尾且表现良好的区间的长度。
# 如果在cnt前面没有找到cnt-1的值出现的位置,则以cnt作为结尾且表现良好的区间的长度设置为0
# 注:本题中前缀和数组是连续的,且相邻两个值相差为1,因而只需要在cnt的前面寻找比cnt小1的数。
if cnt - 1 not in idx.keys():
fn[cnt] = 0
else:
# 在cnt前面出现了比cnt小1的值,则使用递推公式求以cnt作为结尾,且表现良好的区间的长度。
# 递推公式:f(n) = f(n-1) + p(n) - p(n-1),其中p(n)表示值n此刻出现的位置,p(n-1)表示值n-1第一次出现的位置。
fn[cnt] = fn[cnt - 1] + (i - idx[cnt - 1])
# 第四步:实时计算以 前缀和数组中的每个值作为结尾时,表现良好的区间的长度,并更新最长的长度。
# 如果cnt在之前出现过,且比cnt小1的值也出现过,则可使用递推公式求此刻以cnt作为结尾且表现良好的区间的长度,并更新最长区间的长度。
# 如果比cnt小1的值在前面没有出现过,则此刻以cnt作为结尾且表现良好的区间长度为0,则不用更新最长区间的长度。
# 注意,不能使用answer = max(answer, f[cnt]); 因为f[cnt]表示cnt第一次出现时表现良好的区间的长度。而在前缀和数组中,cnt的值等于
# 同一个值的情况可能在多个位置出现,因而在求表现良好的最长区间时,要时实计算以cnt作为结尾且表现良好的区间的长度。
# 如图中1出现在了多个位置,而f[1]表示值1第一次出现时表现良好的区间长度。因此要重新计算f[cnt - 1] + i - ind[cnt - 1]的大小。
if cnt - 1 in idx.keys():
length = fn[cnt - 1] + i - idx[cnt - 1] # 实时计算此刻以cnt最为结尾时表现良好的区间的长度。
answer = max(answer, length)
return answer
def main():
solution = Solution()
hours = [9, 9, 6, 0, 6, 6, 9]
print(solution.longestWPI(hours))
if __name__ == "__main__":
main()
说明
- 对应LeetCode第1124题。
- 链接:https://leetcode-cn.com/problems/longest-well-performing-interval/