本文涉及知识点
动态规划 子集状态压缩 回溯
动态规划汇总
LeetCode1723. 完成所有工作的最短时间
给你一个整数数组 jobs ,其中 jobs[i] 是完成第 i 项工作要花费的时间。
请你将这些工作分配给 k 位工人。所有工作都应该分配给工人,且每项工作只能分配给一位工人。工人的 工作时间 是完成分配给他们的所有工作花费时间的总和。请你设计一套最佳的工作分配方案,使工人的 最大工作时间 得以 最小化 。
返回分配方案中尽可能 最小 的 最大工作时间 。
示例 1:
输入:jobs = [3,2,3], k = 3
输出:3
解释:给每位工人分配一项工作,最大工作时间是 3 。
示例 2:
输入:jobs = [1,2,4,7,8], k = 2
输出:11
解释:按下述方式分配工作:
1 号工人:1、2、8(工作时间 = 1 + 2 + 8 = 11)
2 号工人:4、7(工作时间 = 4 + 7 = 11)
最大工作时间是 11 。
提示:
1 <= k <= jobs.length <= 12
1 <= jobs[i] <= 107
动态规划
n = jobs.length
vNeed[mask]记录 一个工人完成mask工作需要的时间。
动态规划的状态表示
dp[i][mask] 表示前i个工人完成了mask任务的最大工作时间。
用滚动向量 优化空间,pre[mask]表示dp[i][mask] dp[mask]表示dp[i+1][mask]。
空间复杂度: O(2n)
动态规划的转移方程
枚 举 c u r M a s k = 1 m a s k C o u n t − 1 枚 举 c u r S e l = c u r M a s k 的非空子集 d p [ c u r M a s k ] = m i n ( d p [ c u r M a s k ] , m a x ( p r e [ c u r M a s k − c u r S e l ] , v N e e d [ c u r S e l ] ) ) 枚举_{curMask=1}^{maskCount-1}枚举_{curSel=curMask的非空子集}\\ dp[curMask] = min(dp[curMask],max(pre[curMask-curSel],vNeed[curSel])) 枚举curMask=1maskCount−1枚举curSel=curMask的非空子集dp[curMask]=min(dp[curMask],max(pre[curMask−curSel],vNeed[curSel]))
动态规划的填表顺序
依次处理各工人
动态规划的初始值
pre[0]=0,其它表示不存在的大值。
动态规划的返回值
pre.back
动态规划的代码
核心代码
class Solution {
public:
int minimumTimeRequired(vector<int>& jobs, int k) {
const int n = jobs.size();
const int maskCount = 1 << n;
vector<int> vNeed(maskCount);
for (int i = 0; i < n; i++) {
vNeed[1 << i] = jobs[i];
}
for (int mask = 1; mask < maskCount; mask++) {
const int end = mask & (-mask);
vNeed[mask] = vNeed[mask - end] + vNeed[end];
}
vector<int> pre(maskCount, m_iNotMay);
pre[0] = 0;
for (int i = 0; i < k; i++) {
auto dp = pre;
for (int curMask = 1; curMask < maskCount; curMask++) {
for (int curSel = curMask; curSel; curSel = (curSel - 1) & curMask) {
dp[curMask] = min(dp[curMask], max(pre[curMask - curSel] , vNeed[curSel]));
}
}
pre.swap(dp);
}
return pre.back();
}
const int m_iNotMay = 1'000'000'000;
};
测试用例
template<class T>
void Assert(const vector<T>& v1, const vector<T>& v2)
{
if (v1.size() != v2.size())
{
assert(false);
return;
}
for (int i = 0; i < v1.size(); i++)
{
assert(v1[i] == v2[i]);
}
}
template<class T>
void Assert(const T& t1, const T& t2)
{
assert(t1 == t2);
}
int main()
{
vector<int> jobs;
int k;
{
Solution slu;
jobs = { 3, 2, 3 }, k = 3;
auto res = slu.minimumTimeRequired(jobs, k);
Assert(3, res);
}
{
Solution slu;
jobs = { 1,2,4,7,8 }, k = 2;
auto res = slu.minimumTimeRequired(jobs, k);
Assert(11, res);
}
}
回溯
直接回溯超时,需要剪枝,过于复杂。本代码超时。
class Solution {
public:
int minimumTimeRequired(vector<int>& jobs, int k) {
const int n = jobs.size();
sort(jobs.begin(), jobs.end(), greater<>());
vector<int> vWork(k);
int iRet = 1'000'000'000;
function<void(int,int, vector<int>)> BackTrack = [&](int leve,int iMax, vector<int> vWork) {
if (iMax >= iRet) { return; }
if (jobs.size() == leve) {
iRet = min(iRet, iMax);
return;
}
sort(vWork.begin(), vWork.end());
for (int i = 0; i < k; i++) {
vWork[i] += jobs[leve];
BackTrack(leve + 1,max(iMax, vWork[i]),vWork);
vWork[i] -= jobs[leve];
}
};
BackTrack(0, 0,vWork);
return iRet;
}
};
扩展阅读
视频课程
有效学习:明确的目标 及时的反馈 拉伸区(难度合适),可以先学简单的课程,请移步CSDN学院,听白银讲师(也就是鄙人)的讲解。
https://edu.csdn.net/course/detail/38771
如何你想快速形成战斗了,为老板分忧,请学习C#入职培训、C++入职培训等课程
https://edu.csdn.net/lecturer/6176
相关下载
想高屋建瓴的学习算法,请下载《喜缺全书算法册》doc版
https://download.csdn.net/download/he_zhidan/88348653
我想对大家说的话 |
---|
《喜缺全书算法册》以原理、正确性证明、总结为主。 |
闻缺陷则喜是一个美好的愿望,早发现问题,早修改问题,给老板节约钱。 |
子墨子言之:事无终始,无务多业。也就是我们常说的专业的人做专业的事。 |
如果程序是一条龙,那算法就是他的是睛 |
测试环境
操作系统:win7 开发环境: VS2019 C++17
或者 操作系统:win10 开发环境: VS2022 C++17
如无特殊说明,本算法用**C++**实现。