内卷大厂系列《动态规划-最少方案贴纸数》

72

持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第4天,点击查看活动详情

《动态规划练习题-最少方案贴纸数》,您将学到如何利用暴力递归改动态规划,一切从尝试入手,如何定义递归函数的含义也是有套路的,模型就是要还是不要,选还是不选的问题上做文章。

一、题目

给定一组贴纸int[][] stickers,每个贴纸sticker有两个属性,分别代表有趣目标值funnyGoal,进攻目标值offenseGoal,要求给定入参有趣目标值和进攻目标值,找到一个最少方案数,让funnyGoal,offenseGoal,都大于等于给定的入参值

二、分析

在给定stickers中选择最少贴纸方案数,使得选择的所有贴纸中的有趣目标值加起来大于等于给定的有趣目标值,并且选择的所有贴纸中的进攻目标值加起来大于等于给定的进攻目标值。

核心思路:当前贴纸要还是不要,选还是不选的问题。

方法一:递归实现,定义递归函数的含义,当前来到index位置,arr[0...index]不要管,已经做好了选择,从arr[index...]开始做选择,当restFunny(剩余有趣值)和restOffense(剩余进攻值)小于等于0 时,即停止,也就是我们的 base case,否则说明还有有趣值和进攻值,接下来就是要还是不要的问题,选还是不选的问题,熟悉的套路,熟悉的秘方。不要当前的有趣值和进攻值,是一种方案,要当前的有趣值和进攻值是另一种方案,最后两种方案选最小。

方法二:从递归实现改动态规划

分析可变参数,只有三个,index,restFunny,restOffense,所以定义一个三维数组,为了提高效率加上记忆化搜索(加缓存)

进一步优化:分析严格位置的依赖关系,自行发挥

三、实现

1、递归

public static int minStickers(int[][] stickers, int funnyGoal, int offenseGoal) {
    return process(stickers, 0, funnyGoal, offenseGoal);
}

// arr[i][0] : 有趣值
// arr[i][1] : 进攻值
// arr[index...] 所有的方案自由选择
// 必须让restFunny、restOffense值 <= 0
// 返回最小的方案数量(index...)
public static int process(int[][] stickers, int index, int restFunny, int restOffense) {
    if (restFunny <= 0 && restOffense <= 0) { // base case
        return 0;
    }
    // 有的值,还没扣完
    if (index == stickers.length) {
        return Integer.MAX_VALUE; // 无效值
    }
    // 有的值还没扣完 但是还有方案可选
    // 不要index号方案
    int p1 = process(stickers, index + 1, restFunny, restOffense);
    // 要用index号方案
    int p2 = Integer.MAX_VALUE;
    int next = process(stickers, index + 1, restFunny - stickers[index][0], restOffense - stickers[index][1]);
    if (next != Integer.MAX_VALUE) {
        p2 = 1 + next; // 1代表要当前方案,next代表后续返回的方案
    }
    return Math.min(p1, p2);
}

2、动态规划

public static int minStickers2(int[][] stickers, int funnyGoal, int offenseGoal) {
    int[][][] dp = new int[stickers.length][funnyGoal + 1][offenseGoal + 1];
    for (int i = 0; i < stickers.length; i++) {
        for (int j = 0; j <= funnyGoal; j++) {
            for (int k = 0; k <= offenseGoal; k++) {
                dp[i][j][k] = -1;
            }
        }
    }
    return process2(stickers, 0, funnyGoal, offenseGoal, dp);
}

public static int process2(int[][] stickers, int index, int restFunny, int restOffense, int[][][] dp) {
    if (restFunny <= 0 && restOffense <= 0) {
        return 0;
    }
    if (index == stickers.length) {
        return Integer.MAX_VALUE;
    }
    if (dp[index][restFunny][restOffense] != -1) {
        return dp[index][restFunny][restOffense];
    }
    // 不选当前的贴纸
    int p1 = process2(stickers, index + 1, restFunny, restOffense, dp);
    // 选当前贴纸
    int p2 = Integer.MAX_VALUE;
    int next2 = process2(stickers, index + 1, Math.max(0, restFunny - stickers[index][0]),
            Math.max(0, restOffense - stickers[index][1]), dp);
    if (next2 != Integer.MAX_VALUE) {
        p2 = next2 + 1;
    }
    int ans = Math.min(p1, p2);
    dp[index][restFunny][restOffense] = ans;
    return ans;
}