1953-你可以工作的最大周数

Raphael Liu Lv10

给你 n 个项目,编号从 0n - 1 。同时给你一个整数数组 milestones ,其中每个 milestones[i]
表示第 i 个项目中的阶段任务数量。

你可以按下面两个规则参与项目中的工作:

  • 每周,你将会完成 某一个 项目中的 恰好一个 阶段任务。你每周都 必须 工作。
  • 连续的 两周中,你 不能 参与并完成同一个项目中的两个阶段任务。

一旦所有项目中的全部阶段任务都完成,或者仅剩余一个阶段任务都会导致你违反上面的规则,那么你将 停止工作
。注意,由于这些条件的限制,你可能无法完成所有阶段任务。

返回在不违反上面规则的情况下你 最多 能工作多少周。

示例 1:

**输入:** milestones = [1,2,3]
**输出:** 6
**解释:** 一种可能的情形是:
​​​​- 第 1 周,你参与并完成项目 0 中的一个阶段任务。
- 第 2 周,你参与并完成项目 2 中的一个阶段任务。
- 第 3 周,你参与并完成项目 1 中的一个阶段任务。
- 第 4 周,你参与并完成项目 2 中的一个阶段任务。
- 第 5 周,你参与并完成项目 1 中的一个阶段任务。
- 第 6 周,你参与并完成项目 2 中的一个阶段任务。
总周数是 6 。

示例 2:

**输入:** milestones = [5,2,1]
**输出:** 7
**解释:** 一种可能的情形是:
- 第 1 周,你参与并完成项目 0 中的一个阶段任务。
- 第 2 周,你参与并完成项目 1 中的一个阶段任务。
- 第 3 周,你参与并完成项目 0 中的一个阶段任务。
- 第 4 周,你参与并完成项目 1 中的一个阶段任务。
- 第 5 周,你参与并完成项目 0 中的一个阶段任务。
- 第 6 周,你参与并完成项目 2 中的一个阶段任务。
- 第 7 周,你参与并完成项目 0 中的一个阶段任务。
总周数是 7 。
注意,你不能在第 8 周参与完成项目 0 中的最后一个阶段任务,因为这会违反规则。
因此,项目 0 中会有一个阶段任务维持未完成状态。

提示:

  • n == milestones.length
  • 1 <= n <= 105
  • 1 <= milestones[i] <= 109

方法一:贪心

提示 1

考虑耗时最长的工作。假设我们需要 longest 周完成该工作,其余工作共计需要 rest 周完成。那么可以完成所有工作的充要条件是:

\textit{longest} \le \textit{rest} + 1.

提示 1 解释

首先考虑必要性。必要性可以通过证明逆否命题,即「如果 longest} > \textit{rest} + 1,那么无法完成所有的工作」,来证明。

我们可以利用反证法,如果可以完成所有工作,那么耗时最长的工作一定可以完成,这意味着至少需要有 longest} - 1 周剩余工作可以被分配在间隔内,但剩余工作的工时 rest 并不满足这一要求,因此充分性得证。

随后考虑充分性。充分性可以通过构造分配方案来证明。我们可以将分配工作时间的过程转化为在 [1, \textit{longest} + \textit{rest}] 闭区间内分配整数的过程,其中每个整数代表对应的一周时间。在分配整数的过程中,我们首先按照从小到大的顺序分配所有的奇数,然后按照从小到大的顺序分配所有的偶数

我们将所有工作按照耗时从高到低来排序,按照前文的顺序分配对应的时间。此时由于 longest} \le \textit{rest} + 1,因此耗时最长的工作不会出现任意两周相邻这种违反规定的情况。类似地可以证明,其他工作由于耗时小于最长的工作,也不会出现相邻的情况。因此必要性得证。

思路与算法

根据 提示 1,我们首先计算出耗时最长的工作所需周数 longest 与剩余工作所需周数 rest,并比较两者大小。根据比较的结果不同会有两种情况:

  • longest} \le \textit{rest} + 1,此时根据 提示 1,所有工作都可以完成,我们返回所有工作的总耗时 longest} + \textit{rest 作为答案。

  • longest} > \textit{rest} + 1,此时我们无法完成耗时最长的工作。根据 提示 1 的证明过程,耗时最长的工作最多可以完成 rest} + 1 周,因此最大的工作周数即为 2 \times \textit{rest} + 1,我们返回该数作为答案。

最后,由于 rest 可能超过 32 位整数的范围,我们需要使用 64 位整数进行相应的计算与比较。

代码

[sol1-C++]
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Solution {
public:
long long numberOfWeeks(vector<int>& milestones) {
// 耗时最长工作所需周数
long long longest = *max_element(milestones.begin(), milestones.end());
// 其余工作共计所需周数
long long rest = accumulate(milestones.begin(), milestones.end(), 0LL) - longest;
if (longest > rest + 1){
// 此时无法完成所耗时最长的工作
return rest * 2 + 1;
}
else {
// 此时可以完成所有工作
return longest + rest;
}
}
};
[sol1-Python3]
1
2
3
4
5
6
7
8
9
10
11
12
class Solution:
def numberOfWeeks(self, milestones: List[int]):
# 耗时最长工作所需周数
longest = max(milestones)
# 其余工作共计所需周数
rest = sum(milestones) - longest
if longest > rest + 1:
# 此时无法完成所耗时最长的工作
return rest * 2 + 1
else:
# 此时可以完成所有工作
return longest + rest

复杂度分析

  • 时间复杂度:O(n),其中 n 为 milestones 的长度。即为遍历数组计算耗时总和与最大值的时间复杂度。

  • 空间复杂度:O(1)。

 Comments
On this page
1953-你可以工作的最大周数