欢迎关注更多精彩
关注我,学习常用算法与数据结构,一题多解,降维打击。
[1723] 完成所有工作的最短时间
给你一个整数数组 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] <= 10^7
此题考查的是子集生成算法和对集合状态压缩的运用,与[1681] 最小不兼容性有些类似。可以用状态压缩dp 或 搜索+剪枝的算法来解决。我也有尝试过用二分查找,但还是基于搜索来实现的,最终验证复杂度过高没有通过了。
思考
题目本质上是把一堆数字分成几个集合,然后使得集合总和最大的值最小化。
由于数字最多只有12个,完全可以用一个int32位表示一个集合。
具体可以看一下这篇,[1681] 最小不兼容性
那么集合的种类就是有1< 题目就转化成 从0到1< 如何确保每种组合只选择一次。 剪枝条件 [1681] 最小不兼容性 设目前任务集合为s, 人数为n。 dp[n][s] 表示 n个人,任务集合为s的情况下最大个人用时。 对于任务数量为x,人数为n,dp[n][(1< 初始状态: dp[i][0] = 0没有任务都是0 dp[0][j] = INTMAX32 (j>0) 0人,还有任务,不可能完成,这里给一个极大值。 转移方程: 每次我们尝试为当前(第i个)工人选择一个j的子集k(k&j == k),作为他的工作。 那么最终个人最大用时就是 max(cost[k], dp[i-1][j-k]) dp[i][j] = min(max(cost[k], dp[i-1][j-k])) (k&j==k) 以上是大致的思路,实际代码中有利用贪心原理进行一些常数优化。 利用DAG模型求解动态规划问题 利用DAG模型求解动态规划问题 [1681] 最小不兼容性 本人码农,希望通过自己的分享,让大家更容易学懂计算机知识。方法一 巧用数字表示集合+搜索+剪枝
分析
思路
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a > b {
return b
}
return a
}
type M struct {
minCost int // 存储当前最优解
jobsNum []int // 存储集合中的任务数
setCost []int // 存储集合用时
}
func (m *M) dfs(maxJobsNum, maxCostBefore, leftJobs, leftWorkers int) {
// 条件一:当前最大用时大于等于最优解
// 条件二:任务已经分配完毕
// 条件三:任务未分配完,人员已经用完,直接返回
// 条件四:根据贪心原理,如果人数大于等于任务数,那么就只有一种情况,大家一人分一个,计算并更新最优解返回。
// 选择的编号要比之前的都要大。 i>leftJobs 时,i&leftJobs != i 总是成立的。
for i:=maxJobsNum+1;i<=leftJobs ;i++ {
// 条件五: 选择的工作集不在剩下的里面
if i&leftJobs != i{
continue
}
m.dfs(max(i, m.jobsNum[i]), max(maxCostBefore, m.setCost[i]), leftJobs-i,leftWorkers-1)
}
}
func (m *M) GetMinCost(n, k int) int {
m.minCost = m.setCost[len(m.setCost)-1] // 默认全给一人个做
m.dfs(0,0, (1<<uint(n))-1, k)
return m.minCost
}
func minimumTimeRequired(jobs []int, k int) int {
m := &M{}
m.setCost = getCost(jobs) // 计算所有集合用时
m.jobsNum = getJobsNum(jobs) // 计算集合作务总数
return m.GetMinCost(len(jobs), k)
}
注意
知识点
复杂度
参考
代码实现
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a > b {
return b
}
return a
}
func getJobsNum(jobs []int) []int{
cnt := make([]int, (1<<uint(len(jobs))))
cnt[0] = 0
for i:=1; i< (1<<uint(len(jobs)));i++ {
cnt[i] = cnt[i>>1]
if i&1>0 {cnt[i]++}
}
return cnt
}
func getCost(jobs []int) []int{
cost := make([]int, (1<<uint(len(jobs))))
for i:=0; i< (1<<uint(len(jobs)));i++ {
cost[i] = 0
for j:=uint(0);int(j)<len(jobs);j++ {
if (1<<j) & i == 0{ continue}
cost[i] += jobs[j]
}
}
return cost
}
type M struct {
minCost int // 存储当前最优解
jobsNum []int // 存储集合中的任务数
setCost []int // 存储集合用时
}
func (m *M) dfs(maxJobsNum, maxCostBefore, leftJobs, leftWorkers int) {
// 条件一:当前最大用时大于等于最优解
if maxCostBefore>= m.minCost {
return
}
// 条件二:任务已经分配完毕
if leftJobs==0 {
m.minCost = min(maxCostBefore, m.minCost)
return
}
// 条件三:任务未分配完,人员已经用完,直接返回
if leftWorkers == 0{
return
}
// 条件四:根据贪心原理,如果人数大于等于任务数,那么就只有一种情况,大家一人分一个,计算并更新最优解返回。
if m.jobsNum[leftJobs]<=leftWorkers {
for i:=uint(0);(1<<i)<=leftJobs;i++ {
if (1<<i)&leftJobs != (1<<i) {continue}
maxCostBefore = max(maxCostBefore, m.setCost[1<<i])
}
m.minCost = min(maxCostBefore, m.minCost)
return
}
// 选择的编号要比之前的都要大。 i>leftJobs 时,i&leftJobs != i 总是成立的。
for i:=maxJobsNum+1;i<=leftJobs ;i++ {
// 条件五: 选择的工作集不在剩下的里面
if i&leftJobs != i{
continue
}
m.dfs(max(i, m.jobsNum[i]), max(maxCostBefore, m.setCost[i]), leftJobs-i,leftWorkers-1)
}
}
func (m *M) GetMinCost(n, k int) int {
m.minCost = m.setCost[len(m.setCost)-1] // 默认全给一人个做
m.dfs(0,0, (1<<uint(n))-1, k)
return m.minCost
}
func minimumTimeRequired(jobs []int, k int) int {
m := &M{}
m.setCost = getCost(jobs) // 计算所有集合用时
m.jobsNum = getJobsNum(jobs) // 计算集合作务总数
return m.GetMinCost(len(jobs), k)
}
/*
func main() {
fmt.Println(minimumTimeRequired([]int{3,2,3}, 3))
fmt.Println(minimumTimeRequired([]int{1,2,4,7,8}, 2))
fmt.Println(minimumTimeRequired([]int{12343,2223,4222,721,82323,3923,222,1122,34563,29309,222,33445}, 10))
fmt.Println(minimumTimeRequired([]int{6518448,8819833,7991995,7454298,2087579,380625,4031400,2905811,4901241,8480231,7750692,3544254}, 4))
}
*/
方法二 巧用数字表示集合+DAG+动态规划
分析
思路
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a > b {
return b
}
return a
}
func minimumTimeRequired(jobs []int, k int) int {
setCost := getCost(jobs) // 计算集合用时
jobCnt := getJobsNum(jobs) // 计算集合中的任务
dp := initDp(len(jobs), k+1) // 申请内存
// 初始化
for i, _ := range dp[0] {
}
for i:=1;i<=k;i++ {
dp[i][0]=0 // 没有任务
for j:=1;j<len(dp[i]);j++ {
// 人比任务多,直接复制少一人的答案。
// 默认让一个人做
for selectJobs := 0;selectJobs<j;selectJobs++ {
// 选择的任务不是J的子集。
}
}
}
//showDp(dp)
return dp[k][(1<<uint(len(jobs)))-1]
}
注意
知识点
复杂度
参考
代码实现
func max(a, b int) int {
if a > b {
return a
}
return b
}
func min(a, b int) int {
if a > b {
return b
}
return a
}
func getJobsNum(jobs []int) []int{
cnt := make([]int, (1<<uint(len(jobs))))
cnt[0] = 0
for i:=1; i< (1<<uint(len(jobs)));i++ {
cnt[i] = cnt[i>>1]
if i&1>0 {cnt[i]++}
}
return cnt
}
func getCost(jobs []int) []int{
cost := make([]int, (1<<uint(len(jobs))))
for i:=0; i< (1<<uint(len(jobs)));i++ {
cost[i] = 0
for j:=uint(0);int(j)<len(jobs);j++ {
if (1<<j) & i == 0{ continue}
cost[i] += jobs[j]
}
}
return cost
}
func initDp(n, k int) [][]int{
dp := make([][]int, k)
for i, _ := range dp {
dp[i] = make([]int, 1<<uint(n))
}
return dp
}
func showDp(dp [][]int) {
//fmt.Printf("%03b", 2)
fmt.Print(" ")
for i:=0;i<len(dp[0]);i++ {
fmt.Printf("%03b ", i)
}
fmt.Println()
for i, v := range dp {
fmt.Printf("%d", i)
for _, x := range v {
fmt.Printf("%4d", x)
}
fmt.Println()
}
fmt.Println()
}
func minimumTimeRequired(jobs []int, k int) int {
setCost := getCost(jobs)
jobCnt := getJobsNum(jobs)
dp := initDp(len(jobs), k+1)
for i, _ := range dp[0] {
if i==0 { // 没有任务0时完成
dp[0][0]=0
continue
}
// 0人多任务, 无法完成
dp[0][i] = math.MaxInt32
}
for i:=1;i<=k;i++ {
dp[i][0]=0 // 没有任务
for j:=1;j<len(dp[i]);j++ {
if i>jobCnt[j] { // 人比任务多,直接复制少一人的答案。
dp[i][j] = dp[i-1][j]
continue
}
dp[i][j] = setCost[j] // 默认让一个人做
for selectJobs := 0;selectJobs<j;selectJobs++ {
if selectJobs&j != selectJobs {continue}// 选择的任务不是J的子集。
dp[i][j] = min(dp[i][j], max(setCost[selectJobs], dp[i-1][j-selectJobs]))
}
}
}
//showDp(dp)
return dp[k][(1<<uint(len(jobs)))-1]
}
相关题目