换钱的方法数

题目

给定数组 arr,arr 中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim,代表要找的钱数,求换钱有多少种方法。

举例

arr=[5,10,25,1],aim=0.
组成0元的方法有1种,就是所有面值的货币都不用。所以返回1。

arr=[5,10,25,1],aim=15。
组成15元的方法有6种,分别为3张5元、1张10元+1张5元、1张10元+5张1元、10张1元+1张5元、2张5元+5张1元和15张1元。所以返回6。

arr=[3,5],aim=2.
任何方法都无法组成2元。所以返回0。

解答

题目其实可以转化为 aim 由 arr 中的数组成,arr中的数可以重复使用,使用的总次数最少为多少。
这里可以使用动态规划的解法,假设 dp[i] 表示使用arr中数组成i的最少数目,动态规划公式如下所示
dp[i] = min(dp[i - vec[j]]) + 1 ,j >= 0 && j <= arr.size()
dp初始为 aim 大小且每个值为 -1 的数组,代码如下所示,具体思路可以参考《程序员代码面试指南》书籍

代码实现

#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;

class solution {
public:
    int searchMoney(const vector<int>& vec, int aim) {
        if (0 == aim) {
            return 0;
        }
        vector<int> dp(aim + 1, -1);
        dp[0] = 0;
        
        for (int i = 1; i <= aim; i++) {
            for (int j = 0; j < vec.size(); j++) {
                if (i < vec[j]) {
                    // i小于 vec[j] 表示i无法由vec[j]组成
                    continue;
                } else if (i == vec[j]) {
                    // i 等于 vec[j] 表示i刚好可以由vec[j]组成
                    dp[i] = 1;
                } else if (dp[i - vec[j]] != -1){
                    // dp[i - vec[j]] 为-1的话,表示i - vec[j]无法组成
                    dp[i] = -1 == dp[i] ? dp[i - vec[j]] + 1 : min(dp[i], dp[i - vec[j]] + 1);                   
                }
            }      
        }   
        return dp[aim];
    }
};

int main(int argc, const char* argv[]) {
    //arr = [5, 2, 3] aim 为 20
    //arr = [5, 2, 3] aim 为 0
    //arr = [5, 3] aim 为 2
    solution a;
    vector<int> v1 = {5, 2, 3};
    cout << a.searchMoney(v1, 5) << endl;
    cout << a.searchMoney(v1, 0) << endl;
    cout << a.searchMoney(v1, 1) << endl;
    return 0;
}
这个问题是一个经典的**组合取前k小和问题**,我们需要从每个箱子中各选一个宝物,然后找出所有可能组合的总价值中的前k小值,并求它们的总和。 --- ## ✅ 解题思路 ### 🧠 问题核心: - 给你 `n` 个箱子,每个箱子有若干个物品 - 每次从每个箱子中选择一个物品组成一个组合组合的价值是这些物品的和 - 要求输出前 `k` 小的和的总和(注意去重?题目允许重复) ### 💡 关键点: - 如果暴力枚举所有组合(最多 $100^n$),会爆炸! - 使用 **最小堆 + 动态规划思想** 来维护当前最小的组合和 - 初始先把每个箱子都选第一个最小素,计算初始和 - 然后使用优先队列逐步扩展出前 k 小的和 --- ## ✅ Java 实现(ACM 模式) ```java import java.io.*; import java.util.*; public class Main { public static void main(String[] args) throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); String[] nk = br.readLine().split(" "); int n = Integer.parseInt(nk[0]); int k = Integer.parseInt(nk[1]); List<List<Integer>> lists = new ArrayList<>(); for (int i = 0; i < n; i++) { String[] parts = br.readLine().split(" "); int m = Integer.parseInt(parts[0]); List<Integer> list = new ArrayList<>(); for (int j = 1; j <= m; j++) { list.add(Integer.parseInt(parts[j])); } Collections.sort(list); // 每个箱子先排序 lists.add(list); } // 最小堆:保存当前的和与对应的每个箱子选取的位置索引 PriorityQueue<State> minHeap = new PriorityQueue<>((a, b) -> a.sum - b.sum); // 用于记录已经加入过的状态,避免重复 Set<String> visited = new HashSet<>(); // 初始状态:每个箱子都选第一个素 int[] indices = new int[n]; int initialSum = 0; for (int i = 0; i < n; i++) { initialSum += lists.get(i).get(0); } String key = Arrays.toString(indices); minHeap.offer(new State(initialSum, indices.clone())); visited.add(key); long total = 0; int count = 0; while (!minHeap.isEmpty() && count < k) { State curr = minHeap.poll(); total += curr.sum; count++; // 对于每一个箱子尝试往后移动一位 for (int i = 0; i < n; i++) { if (curr.indices[i] < lists.get(i).size() - 1) { int[] newIndices = curr.indices.clone(); newIndices[i] += 1; String newKey = Arrays.toString(newIndices); if (!visited.contains(newKey)) { int newSum = curr.sum - lists.get(i).get(curr.indices[i]) + lists.get(i).get(newIndices[i]); minHeap.offer(new State(newSum, newIndices)); visited.add(newKey); } } } } System.out.println(total); } static class State { int sum; int[] indices; public State(int sum, int[] indices) { this.sum = sum; this.indices = indices; } } } ``` --- ## 🔍 示例说明 输入: ``` 3 10 4 1 3 4 5 3 1 7 9 4 1 2 3 5 ``` 解释: - 箱子1:[1, 3, 4, 5] - 箱子2:[1, 7, 9] - 箱子3:[1, 2, 3, 5] 我们从每个箱子各选一个数,组合出所有的和,找出前10小的和: ``` 前10小分别为: 3, 4, 5, 5, 6, 6, 7, 7, 7, 7 总和为: 57 ``` 输出: ``` 57 ``` --- ## ❗注意事项 - 每个箱子必须先排序,才能保证每次取最小值 - 使用优先队列(最小堆)来逐步生成下一个最小的组合和 - 使用 `Set<String>` 防止重复的状态被多次加入堆 - 时间复杂度控制在合理范围内(最多 k log k) - 可以处理最大 100 个箱子、每箱 100 个物品的情况 --- ##
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Coder567

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值