我是靠谱客的博主 长情水杯,最近开发中收集的这篇文章主要介绍leetcode 638. Shopping Offers | 638. 大礼包(动态规划,多约束背包问题),觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

题目

https://leetcode.com/problems/shopping-offers/
在这里插入图片描述

题解

类似题目有:leetcode 474. Ones and Zeroes | 474. 一和零(双约束背包问题)

本题实质上是一个多约束(n<6)的背包问题,求解目标是,在背包刚好装满的情况下,希望让总的 price 最小。物品可以使用无限次。

参考左神体系班学习班第19节:剪贴纸问题(实际上没参考它,只是想到有这么个题)

给定一个字符串str,给定一个字符串类型的数组arr,出现的字符都是小写英文
arr每一个字符串,代表一张贴纸,你可以把单个字符剪开使用,目的是拼出str来
返回需要至少多少张贴纸可以完成这个任务。
例子:str= “babac”,arr = {“ba”,“c”,“abcd”}
ba + ba + c 3 abcd + abcd 2 abcd+ba 2
所以本题返回2

由于一些物品是以组合的形式给出的,所以我们先将 list 嵌套 list 转化成数组,方便索引查找。
然后,因为物品可以单独购买。为了统一方便,我们将可以单独购买的物品也转化成组合的形式,只不过让它们的系数都为 1。

如下图所示,前两行分别是单独购买 A 物品、单独购买 B 的价格,后两行是 AB 物品组合购买的价格。
在这里插入图片描述
然后就是经典的 暴力递归 -> 傻缓存了。本题应该是无法转化成 dp 的,因为它的状态太多了,详见 dp map 中的 key,我的处理方式是将状态压缩成了字符串。后来看题解发现,hashmap 可以直接用 list 作为 key。

还有,我这个效率超级低,(应该是我为了避免回溯而copy的数组开销比较大),删掉 import 的头文件的话,会超时(这又是 leetcode 的蜜汁特性,带头文件的话运行速度快一些),带着头文件可以 AC。

import java.util.HashMap;
import java.util.List;
import java.util.Map;

class Solution {
    int M; // 套餐总数
    int N; // 物品总数

    public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
        N = price.size();
        M = special.size() + price.size();

        // 数组化 套餐列表
        int[][] sp = new int[M][N + 1];
        for (int i = 0; i < special.size(); i++) {
            List<Integer> list = special.get(i);
            for (int j = 0; j < N + 1; j++) {
                sp[i][j] = list.get(j);
            }
        }
        for (int i = special.size(), j = 0; i < M; i++, j++) {
            sp[i][j] = 1;
            sp[i][N] = price.get(j);
        }

        // 数组化 购物清单
        int[] need = new int[N];
        for (int i = 0; i < N; i++) {
            need[i] = needs.get(i);
        }
        
        Map<String, Integer> dp = new HashMap<>();
        return process(sp, need, 0, dp);
    }

    // 多约束背包问题
    // 物品列表在special中。当前可选择的物品在cur位置,还剩余needs要买的情况下,至少需要多少cost
    public int process(int[][] special, int[] needs, int cur, Map<String, Integer> dp) {
        // 查缓存
        StringBuilder key = new StringBuilder();
        for (int n : needs) { // 状态压缩成字符串
            key.append(n).append(":");
        }
        key.append(cur);
        if (dp.containsKey(key.toString())) return dp.get(key.toString());

        if (allZero(needs)) {
            dp.put(key.toString(), 0);
            return 0;
        }

        int minCost = Integer.MAX_VALUE;
        for (int p = cur; p < M; p++) { // 当前购买special[p]套餐
            for (int count = 0; canBuy(special, needs, count, p); count++) { // 购买count件p套餐
                int[] newNeeds = buy(special, needs, count, p);
                int newCost = process(special, newNeeds, p + 1, dp);
                if (newCost != Integer.MAX_VALUE) {
                    minCost = Math.min(minCost, count * special[p][N + 1 - 1] + newCost);
                }
            }
        }
        // 缓存
        dp.put(key.toString(), minCost);

        return minCost;
    }

    // 当前状态下,如果继续买count件p物品,是否买了不需要的
    public boolean canBuy(int[][] special, int[] needs, int count, int p) {
        for (int k = 0; k < N; k++) {
            if (needs[k] - count * special[p][k] < 0) return false;
        }
        return true;
    }

    public int[] buy(int[][] special, int[] needs, int count, int p) {
        int[] newNeeds = new int[N];
        for (int k = 0; k < N; k++) {
            newNeeds[k] = needs[k] - count * special[p][k];
        }
        return newNeeds;
    }

    public boolean allZero(int[] needs) {
        for (int n : needs) {
            if (n != 0) return false;
        }
        return true;
    }

}

在这里插入图片描述

最后

以上就是长情水杯为你收集整理的leetcode 638. Shopping Offers | 638. 大礼包(动态规划,多约束背包问题)的全部内容,希望文章能够帮你解决leetcode 638. Shopping Offers | 638. 大礼包(动态规划,多约束背包问题)所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(40)

评论列表共有 0 条评论

立即
投稿
返回
顶部