leetcode
leetcode 551 ~ 600
大礼包

大礼包

难度:

标签:

题目描述

代码结果

运行时间: 40 ms, 内存: 16.9 MB


// Java Stream Solution
// 题目思路:
// 1. 与Java方法类似,使用stream和lambda表达式来简化代码逻辑。
// 2. 使用stream API遍历special大礼包,并计算所有可能的最低价格。
 
import java.util.*;
 
public class Solution {
    public int shoppingOffers(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
        return dfs(price, special, needs);
    }
 
    private int dfs(List<Integer> price, List<List<Integer>> special, List<Integer> needs) {
        int minPrice = needs.stream().mapToInt(i -> price.get(i) * needs.get(i)).sum();
        for (List<Integer> offer : special) {
            List<Integer> clone = new ArrayList<>(needs);
            boolean valid = true;
            for (int i = 0; i < needs.size(); i++) {
                int diff = clone.get(i) - offer.get(i);
                if (diff < 0) {
                    valid = false;
                    break;
                }
                clone.set(i, diff);
            }
            if (valid) {
                minPrice = Math.min(minPrice, offer.get(needs.size()) + dfs(price, special, clone));
            }
        }
        return minPrice;
    }
}

解释

方法:

此题解使用回溯法来寻找最低花费的购买方案。首先,它过滤出那些实际可以节省费用的特殊包裹(即大礼包的总价值小于单独购买包含商品的价格)。然后,从每一个有效的大礼包开始递归尝试,同时更新剩余需要的物品数量和总花费。如果当前尝试的总价已经超过已知的最低价格,则提前结束该路径的探索。此外,如果不能再使用当前大礼包,它会尝试单独购买剩余的每种商品,并更新总花费。

时间复杂度:

O(2^m * n) 其中 m 是有效大礼包的数量,n 是物品种类的数量

空间复杂度:

O(m + n) 其中 m 是大礼包的数量,n 是物品种类的数量

代码细节讲解

🦆
在过滤大礼包时,为什么要检查`sum(sp[i] * price[i] for i in range(n)) > sp[-1]`?这个条件是怎样帮助确保节省成本的?
这个条件用来确保大礼包的购买成本低于单独购买包裹内所有商品的总成本。这里,`sum(sp[i] * price[i] for i in range(n))`计算的是如果单独购买大礼包中的每个商品需要支付的总价格,而`sp[-1]`是购买整个大礼包的价格。如果大礼包的价格低于单独购买所有商品的价格,那么购买大礼包就能节省开支。通过过滤掉那些不具备节省成本优势的大礼包,算法可以专注于那些真正能够减少总支出的购买方案。
🦆
回溯函数中的`special_idx`参数具体是用来做什么的?如何影响递归过程中大礼包的选择?
`special_idx`参数在回溯函数中用于控制从哪个大礼包开始尝试,以避免重复尝试相同的大礼包组合。在递归过程中,每次调用`backtrack`都会从`special_idx`指定的索引开始,这样可以确保每一层递归都考虑所有未尝试过的大礼包。这种策略不仅避免了不必要的重复计算,还能确保算法能够探索所有可能的大礼包组合。
🦆
为什么在尝试每个大礼包时需要创建`updated_remainder`的副本而不是直接修改`remainder`?
在回溯算法中,创建`updated_remainder`的副本是为了保持每一次函数调用的独立性和状态的不变,这样可以正确地返回上一层递归状态进行其他可能的尝试。如果直接修改`remainder`,那么在递归返回后,`remainder`的状态会被改变,这将影响其他递归路径的正确性和结果。使用副本可以确保每次尝试都是基于相同的初始状态,从而正确地探索所有可能的购买方案。
🦆
题解中提到如果当前花费已经不是最优则停止搜索,这种策略是如何帮助优化算法性能的?
这种策略是基于剪枝优化的一种实现。当在递归过程中发现当前总花费已经超过了已知的最低花费时,继续探索当前路径是没有意义的,因为它不可能产生更好的结果。通过提前终止这些不可能是最优解的搜索路径,可以显著减少不必要的计算量,从而提高算法的效率和性能。这种方法确保只有有希望成为最优解的路径才会被完全探索。

相关问题