[牛客BM70&LeetCode322]零钱兑换Ⅰ——DFS,记忆化搜索,动态规划(C++)

题目描述

给你一个整数数组arr,表示不同面额的硬币;以及一个整数aim,表示需要放入钱包的目标金额。
计算并返回可以凑成总金额所需的最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回-1 。
每种硬币的数量无限。

  • 用例1:
    输入:[1, 2, 3], 6
    输出:2(即3+3)

思路一:深度优先搜索

本题自然可以通过遍历所有可能的硬币组合以求得最少的硬币数量。
每次都选择三种面额(以用例1举例)中的一枚放入到钱包中,直到钱包达到目标金额。

上面这个思路其实就是深度优先搜索的方法(DFS)。递归深度就是使用的硬币的个数。
然而这种方式将会出现大量的重复计算,比如用例中:6-2=4,6-1-1=4;导致4这个节点会被多次计算。

如图


值为4的节点和值为3的节点都多次出现。

参考代码如下:

class Solution {
private:
    int _min_depth = INT_MAX;  // 初始化递归深度为(近似)无限大
public:
    void dfs(vector<int> &arr, int rem, int depth) {
        if (rem == 0) {
            _min_depth = min(_min_depth, depth);  // 每次递归到结束条件就更新最小的深度
            return ;
        }

        for (auto coin : arr)
            dfs(arr, rem - coin, depth + 1);  // 每次递归深度+1
    }
    int minMoney(vector<int>& arr, int aim) {
        dfs(arr, aim, 0);
        return _min_depth;
    }
};

思路二:记忆化搜索

根据上文分析,可以将已经出现过的钱包剩余目标金额的最少硬币数记录下来,应该使用数组记录1 ~ aim范围内所有的金额的最少硬币数。

class Solution {
public:
    int dfs(vector<int> &arr, vector<int> &mem, int aim) {
        if (aim < 0) return -1;
        if (aim == 0) return 0;
        if (mem[aim] <= aim) return mem[aim];
        for (int i = 0; i < arr.size(); ++i) {
            int temp = dfs(arr, mem, aim-arr[i]);
            if (temp != -1)
                mem[aim] = min(mem[aim], temp + 1);    
        }

        if (mem[aim] <= aim)
            return mem[aim];
        else
            return mem[aim] = -1;
    }
    int minMoney(vector<int>& arr, int aim) {
        vector<int> mem(aim + 1, aim + 1); // 初始化数组
        return dfs(arr, mem, aim);
    }
};

思路三:动态规划

深度优先搜索采用自上而下的方法,“长”成了一棵树。
动态规划则是自下而上,找到每个节点的最小硬币数,不会是一个树状结构。

class Solution {
public:
    int minMoney(vector<int>& arr, int aim) {
        vector<int> dp(aim + 1, aim + 1);
        dp[0] = 0;
        for (int i = 1; i <= aim; ++i) {
            for (int j : arr) {
                if (i >= j) 
                    dp[i] = min(dp[i], dp[i - j] + 1);
            }
        }        
        
        if (dp[aim] != aim + 1) return dp[aim];
        else return -1;
    }
};

错误的思路:贪心+DFS

有种错误的思路是,认为放尽量多的面额大的硬币就可以让使用硬币的数量最少(即贪心思想)。如果每种面额的硬币只有一枚,那么可以使用贪心思想。
然而,考虑这种情况:如目标金额10,硬币为7,5,1;则最少硬币数是2(5+5)而不是4(7+1+1+1)。
这里也提供错误思路的代码:

class Solution {
  private:
    int _min_depth = INT_MAX;
    int flag = false;
  public:
    void dfs(vector<int>& arr, int rem, int depth) {
        if (rem == 0) {
            flag = true;
            _min_depth = depth;
            return ;
        }

        for (auto coin : arr) {
            if (!flag) dfs(arr, rem - coin, depth + 1);
        }
    }
    int minMoney(vector<int>& arr, int aim) {
        sort(arr.begin(), arr.end(), greater<>());
        dfs(arr, aim, 0);
        return _min_depth == INT_MAX ? -1 : _min_depth;
    }
};
posted @ 2023-03-15 21:14  Luke老黑  阅读(135)  评论(0编辑  收藏  举报