动态规划

Posted tianzeng

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划相关的知识,希望对你有一定的参考价值。

  给定数组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。

暴力解

技术图片

  使用0张200的,后面凑出1000的方法数a

  使用1张200的,后面凑出 800的方法数b

  使用2张200的,后面凑出 600的方法数c

  a+b+c全部加起来就是答案。

优化:记忆化搜索

  如果index和aim固定的,只要是后面要计算600那返回值一定是确定的,是个无后效性问题,前面怎么选择不影响后面的操作。但是返回值一样都要重复计算,利用一个map存储之前的结果(缓存)。下次调用,直接取出,不用这么暴力的重复计算。

动态规划

  参数的变化可以囊括返回值的变化,分析可变参数的变化范围

  1. 目标(主函数调用的递归入口)
  2. 确定不依赖其他位置的值(递归中的basecase,递归出口)
  3. 位置依赖(调递归过程,下一次调用递归的参数)
  4. 优化:当前位置的下一排相同位置及左边的位置

  arr[5,3,2],求组成10的方法总数(表格中每一行的值:当前面值组成当前钱数的方法总数

钱的面值 位置(数组中的下标) 0 1 2 3 4 5 6 7 8 9 10 目标钱数(aim)
5 0 1 1 1 1 1 2 2 2 3 3 4  
3 1 1 1 1 1 1 1 2 1 2 2 2  
2 2 1 1 1 0 1 0 1 0 1 0 1  
  3 1 0 0 0 0 0 0 0 0 0 0  
//给定一些面值的钱(每种钱任意张),求用这些钱组成目标钱数的方法数
#include <iostream>
#include <vector>
#include <string>
#include <map>
using namespace std;

//1.暴力递归
//index:可以任意使用index及其之后的钱
//aim:要找的目标钱数
int get_num_solution(const vector<int> &arr,int index,int aim)
{
    //如果inde==数组的长度,aim还有剩余,那么之前的选择不是有效的res=0
    if(index==arr.size())
        return aim==0?1:0;

    int res=0;
    for(int i=0;arr.at(index)*i<=aim;++i)//一直在选择,此次的选择是否有效,无效后面返回0
        res+=get_num_solution(arr,index+1,aim-arr.at(index)*i);
    return res;
}

//2.优化版---记忆化搜索
//index和aim固定,返回值一定是固定的(无后效性问题):到达一个状态,这个状态和到达它的路径无关,返回值和怎么到达它的无关
//index和aim确定返回值
//key:index_aim     value:返回值
map<string,int> m;//缓存
int get_num_solution1(const vector<int>& arr, int index, int aim)
{
    if(index==arr.size())
        return aim==0?1:0;

    int res=0;
    string key;
    for(int i=0;arr.at(index)*i<=aim;++i)
    {
        int nextAim=aim-arr.at(index)*i;
        key=to_string(index+1).append("_").append(to_string(nextAim));//下一层递归的key
        if(m.count(key)==1)
            res+=m.at(key);
        else
            res+=get_num_solution(arr,index+1,nextAim);
    }
    key=to_string(index).append("_").append(to_string(aim));
    m.insert({key,res});
    return res;
}
//3.动态规划
int get_num_solution2(const vector<int>& arr,int aim)
{
    vector<vector<int> > dp(arr.size(),vector<int>(aim+1,0));
    //第一列
    for(int i=0;i<arr.size();++i)
        dp.at(i).at(0)=1;
    for(int j=1;arr.at(0)*j<=aim;++j)
        dp.at(0).at(arr.at(0)*j)=1;

    for(int i=1;i<arr.size();++i)
    {
        for(int j=1;j<=aim;++j)
        {
            dp.at(i).at(j)=dp.at(i-1).at(j);
            dp.at(i).at(j)+=j-arr.at(i)>=0?dp.at(i).at(j-arr.at(i)):0;
        }
    }
    return dp.at(arr.size()-1).at(aim);
}
int main()
{
    vector<int> a{5,3,2};//{5,10,25,1};
    cout<<get_num_solution(a,0,10)<<endl;
    cout<<get_num_solution1(a,0,10)<<endl;
    cout<<get_num_solution2(a,10)<<endl;
    return 0;
}

 

以上是关于动态规划的主要内容,如果未能解决你的问题,请参考以下文章

是否可以动态编译和执行 C# 代码片段?

动态规划_线性动态规划,区间动态规划

应对笔试手写代码,如何准备动态规划?

应对笔试手写代码,如何准备动态规划?

应对笔试手写代码,如何准备动态规划?

算法动态规划 ⑤ ( LeetCode 63.不同路径 II | 问题分析 | 动态规划算法设计 | 代码示例 )