2742.力扣每日一题6/28 Java(0-1背包问题)

  • 博客主页:音符犹如代码
  • 系列专栏:算法练习
  • 关注博主,后期持续更新系列文章
  • 如果有错误感谢请大家批评指出,及时修改
  • 感谢大家点赞👍收藏⭐评论✍ 

目录

0-1背包问题

思路

解题方法

时间复杂度

空间复杂度


0-1背包问题

0 - 1 背包问题是一个经典的组合优化问题。

问题描述:给定一组物品,每个物品有一定的价值和重量,以及一个背包,背包有一定的容量限制。要求在不超过背包容量的前提下,选择物品放入背包,使得背包中物品的总价值最大。

示例:假设有三个物品,物品 1 价值 6,重量 2;物品 2 价值 10,重量 3;物品 3 价值 12,重量 5,背包容量为 5。

常见解法

  1. 动态规划:创建一个二维数组 dp[i][j] ,其中 i 表示物品索引,j 表示背包容量。通过状态转移方程计算每个状态下的最优值。
    • 状态转移方程:dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]) ,当 j >= weight[i] 。
  2. 回溯法:通过递归地尝试选择或不选择每个物品,来搜索所有可能的组合,找到最优解。但这种方法在物品数量较多时效率较低。

特点

  1. 每个物品只能选择一次(0 - 1 选择),要么放入背包,要么不放入。
  2. 物品的选择具有互斥性。

应用场景

  1. 资源分配:例如在有限的资源下分配项目、任务等。
  2. 投资决策:在有限的资金下选择投资项目。

思路

这里把每堵墙看作一个物品,刷这堵墙的开销 cost[i] 类似于物品的价值,付费油漆匠刷这堵墙所需的时间 time[i] 类似于物品的重量(需要免费油漆匠相应的工作时间)。而 memo 数组用于记录已经计算过的不同状态(已考虑的墙的数量和免费油漆匠已工作的时间)下的最小开销,避免重复计算,从而提高效率。通过递归地考虑选择或不选择当前墙,逐步求解出刷完所有墙的最小开销。

具体来说,对于每堵墙(物品),有两种选择:选择让付费油漆匠刷这堵墙,那么需要消耗相应的时间(类似于背包中放入物品占用的重量),同时加上这堵墙的开销,然后基于剩余的墙和免费油漆匠的剩余工作时间继续递归求解;或者不选择让付费油漆匠刷这堵墙,直接基于前一堵墙的情况继续递归求解。最终通过比较两种选择的结果,取开销较小的作为当前状态的最优解,并记录在 memo 数组中。这样,在后续遇到相同状态时,可以直接使用之前计算得到的结果,避免重复计算。

解题方法

  1. 定义了一个二维数组 memo 用于记忆化搜索,避免重复计算已经求解过的子问题的结果。
  2. paintWalls 方法是主要的入口函数,它调用 dfs 方法进行深度优先搜索。
  3. dfs 方法接受以下参数:
    • i:表示当前考虑的墙的索引。
    • j:表示免费油漆匠已经工作的时间。
    • cost:表示每堵墙的开销数组。
    • time:表示每堵墙付费油漆匠所需的时间数组。
    • memo:记忆化数组。
  4. 首先进行边界情况的判断:
    • 如果 j > i,意味着免费油漆匠工作的时间已经超过了墙的数量,此时开销为0,直接返回。
    • 如果 i < 0,返回一个较大的值(这里使用了 Integer.MAX_VALUE / 2),表示这种情况下的开销是不合理的大。
  5. 计算当前状态在 memo 数组中的索引 k,通过 j + memo.length 得到。
  6. 如果 memo[i][k] 不为 -1,说明之前已经计算过这个子问题,直接返回记忆的值。
  7. 否则,进行递归计算:
    • res1 = dfs(i - 1, j + time[i], cost, time, memo) + cost[i]:表示选择让付费油漆匠刷当前这堵墙(i),那么免费油漆匠会工作 time[i] 时间,然后递归计算前 i - 1 堵墙的最小开销,并加上刷当前墙的开销 cost[i]
    • res2 = dfs(i - 1, j - 1, cost, time, memo):表示不让付费油漆匠刷当前这堵墙(i),免费油漆匠工作时间减1,递归计算前 i - 1 堵墙的最小开销。
  8. 取 res1 和 res2 中的较小值作为当前状态的最小开销,并将其存储到 memo[i][k] 中,以便后续直接使用。
  9. 最终返回计算得到的最小开销。

时间复杂度

O(n²)

空间复杂度

O(n²)

Code

import java.util.Arrays;

public class Solution {
    public int paintWalls(int[] cost, int[] time) {
        int n = cost.length;
        int[][] memo = new int[n][n * 2 + 1];
        for (int[] row : memo) {
            Arrays.fill(row, -1); 
        }
        return dfs(n - 1, 0, cost, time, memo);
    }

    private int dfs(int i, int j, int[] cost, int[] time, int[][] memo) {
        if (j > i) { 
            return 0;
        }
        if (i < 0) { 
            return Integer.MAX_VALUE / 2; 
        }
        int k = j + memo.length; 
        if (memo[i][k]!= -1) { 
            return memo[i][k];
        }
        int res1 = dfs(i - 1, j + time[i], cost, time, memo) + cost[i];
        int res2 = dfs(i - 1, j - 1, cost, time, memo);
        int min = Math.min(res1, res2);
        memo[i][k] = min;
        return min;
    }
}

你必须找到你的宝藏,否则你在途中发现的一切便全都失去了意义。——保罗·柯艾略《牧羊少年奇幻之旅》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值