凑零钱

给你 k 种面值的硬币,面值分别为 c1, c2 … ck,每种硬币的数量无限,再给一个总金额 amount,问你最少需要几枚硬币凑出这个金额,如果不可能凑出,算法返回 -1 。

输入样例:
3 3
1 2 5

输出样例:
3

1、确定 base case,这个很简单,显然目标金额 amount 为 0 时算法返回 0,因为不需要任何硬币就已经凑出目标金额了。

2、确定「状态」,也就是原问题和子问题中会变化的变量。由于硬币数量无限,硬币的面额也是题目给定的,只有目标金额会不断地向 base case 靠近,所以唯一的「状态」就是目标金额 amount。

3、确定「选择」,也就是导致「状态」产生变化的行为。目标金额为什么变化呢,因为你在选择硬币,你每选择一枚硬币,就相当于减少了目标金额。所以说所有硬币的面值,就是你的「选择」。

4、明确 dp 函数/数组的定义。我们这里讲的是自顶向下的解法,所以会有一个递归的 dp 函数,一般来说函数的参数就是状态转移中会变化的量,也就是上面说到的「状态」;函数的返回值就是题目要求我们计算的量。就本题来说,状态只有一个,即「目标金额」,题目要求我们计算凑出目标金额所需的最少硬币数量。所以我们可以这样定义 dp 函数:

dp(n) 的定义:输入一个目标金额 n,返回凑出目标金额 n 的最少硬币数量。

#include<bits/stdc++.h>
using namespace std;
int dp[400];
int N,M;
vector<int>v;
// 1.定义dp数组的含义
// 2.找边界解
// 3.找动态转移方程

// 该问题的思路
// 1.将dp数组定义为:当总金额为i,则dp[i]表示至少需要dp[i]枚金币
// 2.显然当总金额为0时,至少需要0枚金币,当总金额小于0时无解。
// 3.遍历所有面值的金币,当金币面值为n时,且总金额为m时,那么dp[m-n]+1(即总金额为m-n的解加1即为
// 总金额为m所需的金币数

// 自顶而下的动态规划,自顶而下求解子问题,把已求出的结果用dp数组存起来,求解释,先判断该子问题
// 是否已有解,若有解直接使用dp数组内的值。
int dfs(int n){
    if(dp[n])return dp[n];
    if(n==0)return 0;
    if(n<0)return -1;
    int res=0x3f3f3f3;
    for(int i=0;i<N;i++){
        int subproblem=dfs(n-v[i]);
        if(subproblem==-1)continue;
        dp[n-v[i]]=subproblem;
        res=min(res,subproblem+1);
    }
    return res!=-1?res:-1;
}
int main(){
    cin>>N>>M;
    for(int i=0;i<N;i++){
        int a;
        cin>>a;
        v.push_back(a);
    }
    cout<<dfs(M);
    return 0;
}

最长上升子序列

#include<bits/stdc++.h>
using namespace std;

int dp[500],a[500];
int N;
int dfs(int s){
    if(s>=N)return -1;
    if(s==N-1)return 1;
    if(dp[s])return dp[s];
    int Max=0;
    for(int i=s+1;i<N;i++){
        if(a[i]>a[s]){
            int subproblem=dfs(i);
            if(subproblem==-1)continue;
            dp[i]=subproblem;
            Max=max(Max,subproblem);
        }
    }
    return Max+1;
}
int main(){
    std::ios::sync_with_stdio(false);
    cin>>N;
    for(int i=0;i<N;i++){
        cin>>a[i];
    }
    int ans=0;
    for(int i=0;i<N;i++){
        ans=max(dfs(i),ans);
    }
    cout<<ans;
    return 0;
}