问题描述
您要计划d天的作业列表。作业是依赖的(即,要执行第i个作业,您必须完成0
您每天必须至少完成一项任务。工作计划的难度是d天每一天的难度之和。一天的难度是当天完成工作的最大难度。
给出一个整数数组jobDifficulty和一个整数d。第i个工作的难点是jobDifficulty [i]。
返回作业计划的最低难度。如果找不到工作时间表,则返回-1。
Input: jobDifficulty = [6,5,4,3,2,1],d = 2
Output: 7
Explanation: First day you can finish the first 5 jobs,total difficulty = 6.
Second day you can finish the last job,total difficulty = 1.
The difficulty of the schedule = 6 + 1 = 7
from functools import lru_cache
class Solution:
def minDifficulty(self,jobDifficulty: List[int],d: int) -> int:
@lru_cache(None)
def oneDayDifficulty(start,end):
if start + 1 == end:
return jobDifficulty[start]
mid = (start + end) // 2
return max(oneDayDifficulty(start,mid),oneDayDifficulty(mid,end))
@lru_cache(None)
def dp(nextTask,days):
if days == 1:
return oneDayDifficulty(0,nextTask)
res = float("inf")
for startTask in range(days - 1,nextTask):
res = min(res,dp(startTask,days - 1) + oneDayDifficulty(startTask,nextTask))
return res
res = dp(len(jobDifficulty),d)
return -1 if res == float('inf') else res
我不确定时间和空间的复杂度,它将是O(d * n ^ 2)吗? 另外,您对此问题有更好的解决方案吗?
解决方法
我们可以有O(d * n * log n)
。令dp[i][d]
代表分配给第i
天的第d
个任务的最佳选择。然后:
dp[i][d] ->
min (
max(A[j..i]) + dp[j-1][d-1]
) for d - 1 ≤ j ≤ i
我们可以将到目前为止已经看到的所有dp[j][d]
存储在由A[j]
排序的二进制搜索树中,并添加与左侧子树中任何节点关联的最小dp[j-1][d-1]
的修饰A[j] + dp[j-1][d-1]
与右子树中的任何节点关联;以及与之配对的j
。当我们到达A[i]
时,我们在树中查找其位置,并跟踪与高于它的任何节点关联的最小A[j] + dp[j-1][d-1]
和与任何节点关联的最小dp[j-1][d-1]
或低于它。然后我们的选择变为:
dp[i][d] ->
min (
// A[i] is the first value on day d
A[i] + dp[i-1][d-1],// A[i] would not affect this one
A[j_higher] + dp[j_higher-1][d-1],// A[i] becomes the max for this one
A[i] + dp[j_eq_or_lower-1][d-1]
)
where j_higher is the one paired with the minimum A[j] + dp[j-1][d-1]
associated with a higher A[j],and j_eq_or_lower is the one paired
with the minimum dp[j-1][d-1] associated with a lower or equal A[j]
插入{,更新装饰并在O(log n)
中的树中查找,然后我们对每个O(n)
执行d
次。
我们可以利用堆栈使用O(d*N)
空间在O(N)
时间上进行操作。
说明:
dp公式:
公式与here几乎没有什么不同。由于我们可以使用堆栈来计算当日d
迄今发现的最大难度费用。
dp[i][d] - represent minimum difficulty that can be achieved considering d days and first i jobs.
重复发生:
有3种情况:
-
ith
工作是第d天唯一的工作,其余所有工作在d
天之前完成。 - 我们尝试添加到当前日期,看看
A[i]
是否比当前j
之前完成的任何更高难度的工作d
更具难度 - 我们今天
j
之前看到过一些难度较高的工作d
。
dp[i][d] = min( A[i] + dp[i-1][d-1],// ith job is the only job on day d
A[i] + (dp[j][d] - A[j]),// or we try adding to current day and see if A[i] has more difficulty than any previous higher difficulty job j already completed on present day
dp[j][d] // or there is some higher difficulty job `j` we saw previously on present day `d`.
)
C ++实现:
我们维护堆栈以计算到目前为止所见的最高难度作业j
。同样,如在循环中看到的那样,我们只需要前一天的值即可计算当日的值,因此我们在那里节省了空间。
int minDifficulty(vector<int>& A,int D) {
int jobs = A.size();
if (jobs < D) return INT_MAX ; // impossible to do atleast 1 job everyday
int max_difficulty = *max_element(A.begin(),A.end()) + 1;
vector<int> prv_day(jobs,max_difficulty),curr_day(jobs);
for (int days = 0; days < D; ++days) {
vector<int> stack;
for (int i = days; i < jobs; i++) {
curr_day[i] = i > 0 ? prv_day[i - 1] + A[i] : A[i]; // start fresh day with first job as ith
while (!stack.empty() && A[stack.back()] <= A[i]) {
int j = stack.back(); stack.pop_back();
curr_day[i] = min(curr_day[i],curr_day[j] - A[j] + A[i]); // or we try extending and see if A[i] has more difficulty than any previous job j on present day
}
if (!stack.empty()) {
curr_day[i] = min(curr_day[i],curr_day[stack.back()]); // or there is some higher difficulty job we saw previously
}
stack.push_back(i);
}
swap(prv_day,curr_day);
}
return prv_day[jobs - 1];
}
时间复杂度:
每天,我们需要O(N)
的时间进行计算。因此,时间复杂度为O(d*N)
。堆栈中的项目最多被推送和弹出O(N)
次。
额外的空间复杂度:
O(N)
用于存储当前和先前状态,而O(N)
用于存储堆栈。
时间复杂度为รยקคгรђשค
时间复杂度为O(d N ^ 2)。有N d个州,并为 每个状态最多为O(N)。
- 同一件事,实现起来可能有点简单:
class Solution:
def minDifficulty(self,nums,d):
@lru_cache(None)
def depth_first_search(i,d):
if d == 1:
return max(nums[i:])
res,max_days = float('inf'),0
for j in range(i,len(nums) - d + 1):
max_days = max(max_days,nums[j])
res = min(res,max_days + depth_first_search(j + 1,d - 1))
return res
return -1 if len(nums) < d else depth_first_search(0,d)