力扣 5856. 完成任务的最少工作时间段

这篇博客讨论了如何利用动态规划策略解决一个任务分配问题,其中任务耗时和任务周期是关键因素。作者首先介绍了一种状态压缩的方法,通过遍历所有可能的分配方案,确定哪些能在单个任务周期内完成。接着进行第二轮优化,遍历子集寻找更优解。最终,代码示例展示了如何实现这一算法,并强调了状态压缩后的遍历技巧。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

题目来源:https://leetcode-cn.com/problems/minimum-number-of-work-sessions-to-finish-the-tasks/

大致题意:

给定一个任务耗时数组和一个任务周期。 一个任务只能在一个任务周期中完成,如何分配任务可以得到最少的任务周期。任务周期大于等于单个任务耗时的最大值。

思路

状态DP

令 n 为总任务个数。
使用一个长度为 n 的二进制数来表示分配方案,其中从低位到高位,第 i 位 若为 1,则代表 task[i] 在当前方案中。
那么再使用 dp[x] 表示:分配方案为 x 时的任务周期数。初始时都可以设置为最大值。
接下来,

  1. 先使用一轮遍历,遍历所有的分配方案,将可以在一个任务周期中完成的任务标记出来(即dp[x] = 1)
  2. 再遍历所有的分配方案,对于每个分配方案,遍历它的子集,看能否做优化。即将当前分配方案分割成子集与子集的补集后,是否需要的任务周期数更少。
  3. 返回dp[1 << n - 1]

代码:

public int minSessions(int[] tasks, int sessionTime) {
        int n = tasks.length;
        // 使用状态压缩,n位的二进制数表示目前的分配方案
        // 其中从低位到高位,第 i 位 若为 1,则代表 task[i] 在当前方案
        int m = 1 << n;
        // 给出一个默认的最大值
        final int INF = 15;
        int[] dp = new int[m];
        // 初始化为最大值
        Arrays.fill(dp, INF);
        // 一轮遍历出可以在一个时间段解决的分配方案
        for (int i = 0; i < m; i++) {
            int state = i;
            int idx = 0;
            int cost = 0;
            while (state > 0) {
                // 当前尾部是否为1,为1代表子集有当前位
                int choice = state & 1;
                // 将当前位的耗时加入
                if (choice == 1) {
                    cost += tasks[idx];
                }
                idx++;
                state >>= 1;
            }
            // 花费小于sessionTime,代表可以在一个时间段做完该子集
            if (cost <= sessionTime)
                dp[i] = 1;
        }
        
        // 第二轮做优化
        for (int i = 0; i < m; i++) {
            // 若当前分配方案已经是最优解:可在一个时间周期解决
            // 直接跳过
            if (dp[i] == 1) {
                continue;
            }
            // 遍历当前分配方案的所有子集,看能否有更优的分配方式
            // j = (j - 1) & i,可以将 j 逐渐变成 i 的子集
            for (int j = i; j > 0; j = (j - 1) & i) {
                // j 是 i 的子集,j ^ i 即是 子集 j 对于 i 的补集
                // 即 j 和 j^i 的并集是 i
                // 若两子集的分配方案优于当前方案,则替换
                dp[i] = Math.min(dp[i], dp[j] + dp[i^j]);
            }
        }
        // 返回最终所有任务都选中的分配方案
        return dp[m-1];
    }

收获

状态压缩后的遍历方法

即遍历当前状态子集的方法:

// m 为 n位的二进制状态
for (int i = 0; i < m; i++) {
		// 遍历当前状态 i 的所有子集状态
        for (int j = i; j > 0; j = (j-1) & i)
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

三更鬼

谢谢老板!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值