昨天的每日一题又出现前缀和+贪心,分析了半天知道用贪心,但没想到要使用前缀和运算,总结记录下。
力扣题目 3439[重新安排会议得到最多空余时间 I]
eventTime
代表活动总时长,startTime
、endTime
标识活动中有n
个会议,第i
个会议时间为startTime[i], endTime[i]
。
k
代表能移动的会议数量。
要求:移动会议,获得最大的连续没有会议的空闲时间
例:将 [1, 2]
的会议安排到 [2, 3]
,得到空余时间 [0, 2]
。输出:2
我的思路
首先想到动态规划,假设dp[i]
代表到i
能获得到的最长时间,
dp_0
为初始0,dp_i=dp_i-1
...
发现无法建立状态转移方程,通俗说就是没办法通过之前的最长时间获得下1个会的最长时间。
然后思考贪心 做法,发现最优解一定存在于某个连续的 k 个会议子集中;
问题转化 :计算连续的k个会议的空闲时间合并,然后比较那个子集的休息时间最大就是答案。
k个会议就最多会有 k+1 个休息时间。 假设休息时间开始为left,结束为right。总的休息时间就是 right-left-所有会议时间总和。
所以从i=k-1开始遍历,计算出每个子集最大的空闲时间。
前缀和优化 :复杂度为 On
(从k-1遍历到n计算每个子集) * (O1
(计算left与right)*On
(计算会议总和))
会发现总时间负责度到了On^2
。需要优化,这里就需要用前缀和预处理会议时间总和。做到O1
查询结果。
python
class Solution:
def maxFreeTime(self, eventTime: int, k: int, startTime: List[int], endTime: List[int]) -> int:
n = len(startTime)
# 处理边界情况
if n == 0 or k == 0:
return eventTime
# 预处理前缀和数组,pre_sum[i]表示前i个会议的总时长
pre_sum = [0] * (n + 1)
for i in range(n):
pre_sum[i+1] = pre_sum[i] + (endTime[i] - startTime[i])
max_free = 0 # 记录最大空闲时间
# 遍历所有可能的连续k个会议的子集
for i in range(k-1, n):
left, right = 0, 0 # 初始化空闲区间的左右边界
# 计算空闲区间的左边界
if i <= k-1:
# 前k个会议的左边界是0
left = 0
else:
# 非前k个会议的左边界是第i-k个会议的结束时间
left = endTime[i-k]
# 计算空闲区间的右边界
if i < n-1:
# 当前会议不是最后一个会议时,右边界是下一个会议的开始时间
right = startTime[i+1]
else:
# 当前会议是最后一个会议时,右边界是整个活动的结束时间
right = eventTime
# 计算当前连续k个会议的总时长
total_event_time = pre_sum[i+1] - pre_sum[i-k+1]
# 计算当前子集对应的空闲时间
current_free = right - left - total_event_time
# 更新最大空闲时间
max_free = max(max_free, current_free)
return max_free
复杂度分析
- 时间复杂度:O (n),主要来自前缀和数组的预处理和一次遍历。
- 空间复杂度:O (n),主要用于存储前缀和数组。
力扣题目 3440[重新安排会议得到最多空余时间 II]
eventTime
代表活动总时长,startTime
、endTime
标识活动中有n
个会议,第i
个会议时间为startTime[i], endTime[i]
。只能移动1个会议;注意:会议顺序可以改变。
要求:移动会议,获得最大的连续没有会议的空闲时间
我的思路
咋一看还以为是昨天的题目固定K=1的情况,洋洋洒洒写完了才发现不对。
会议顺序可以改变 就导致两个情况:
1、如果其他空位能够安排要移动的第i个会议,答案为right-left
2、如果不能安排,答案为 right-left-(endTime[i]-startTime[i])
如何判断其他空位是否能够安排第i个会议 ?
遍历i, 对所有其他空位计算是否能够放下会议。(复杂度为On^2)
需要用1次遍历计算是否有空位放置会议。使用双向遍历预处理i之前的空格最大值,判断是否能放下i。如果做不到1个循环判断i与n-i+1,可以用两个循环实现。
python
class Solution:
def maxFreeTime(self, eventTime: int, startTime: List[int], endTime: List[int]) -> int:
n = len(startTime)
if n == 0: # 处理边界情况:没有会议
return eventTime
# 预处理数组q:q[i]表示第i个会议是否可以被安排到其他空闲位置
q = [False] * n
# t1 记录从左到右遍历过程中遇到的最大空闲区间长度
# t2 记录从右到左遍历过程中遇到的最大空闲区间长度
t1 = 0
t2 = 0
# 双向遍历预处理每个会议左右两侧的最大空闲区间
for i in range(n):
# 处理第i个会议左侧的空闲区间
current_gap_left = startTime[i] - (0 if i == 0 else endTime[i-1])
if endTime[i] - startTime[i] <= t1:
q[i] = True # 如果当前会议时长小于等于左侧最大空闲区间,则可以安排
t1 = max(t1, current_gap_left) # 更新左侧最大空闲区间
# 处理第n-i-1个会议右侧的空闲区间(从右向左遍历)
j = n - i - 1
current_gap_right = (eventTime if i == 0 else startTime[j+1]) - endTime[j]
if endTime[j] - startTime[j] <= t2:
q[j] = True # 如果当前会议时长小于等于右侧最大空闲区间,则可以安排
t2 = max(t2, current_gap_right) # 更新右侧最大空闲区间
# 计算最大空闲时间
res = 0
for i in range(n):
# 计算移除第i个会议后的左右边界
left = 0 if i == 0 else endTime[i-1]
right = eventTime if i == n-1 else startTime[i+1]
if q[i]:
# 如果第i个会议可以被安排到其他位置,则其原占用时间完全消除
res = max(res, right - left)
else:
# 否则,需要减去该会议的时长
res = max(res, right - left - (endTime[i] - startTime[i]))
return res
复杂度分析
- 时间复杂度:O (n),两次线性遍历和一次结果计算
- 空间复杂度 :O (n),主要用于存储预处理数组
q