动态规划的解题规律
这几天在研究算法,学到动态规划真心感觉这对于我来说是块硬骨头,花了一天多的时间,直到现在才终于有了些收获,发现了动态规划解题的大致模板。
动态规划相较于分治法最大的不同即为动态规划会保留过程中得到的值,为下一次遇到相同的过程时直接调用计算过的结果来节省时间,用空间换效率。
其动态规划的核心函数是通过不断调用自身,来一步一步从终点(最终需求的整体答案)分解到起点(直接可以计算的小个体),而在这个过程中,直接利用存过值的数组来优化整个计算过程。
其一般解题规律可以被涵盖为三句话(对于核心函数):
1. 如果存值数组记录了本次对象对应的值,则直接返回这个已经计算过的值。(例如斐波那契数列的递归方法中,如果要求f(5),向前递归的过程中可能要计算多次f(2)的值,但如果我们将f(2)这个值保存起来,在下一次再次遇到对象值2时直接返回对应的值就会节省好几次计算时间)
2.如果本次调用即为最后一次调用(回到了起点,无法再向前分解了),则返回起点对应的值。
3.如果处于中途过程,则通过自身调用所有情况并对所有情况取最优解。
我用实例来说明上面三句话:
(原题地址:https://www.cnblogs.com/wuyuegb2312/p/3281264.html)
问题:对于序列S和T,它们之间距离定义为:对二者其一进行几次以下的操作(1)删去一个字符;(2)插入一个字符;(3)改变一个字符。每进行一次操作,计数增加1。将S和T变为同一个字符串的最小计数即为它们的距离。给出相应算法。
思路:这道题的最终需求是让求出最小计数,对于两个字符串S和T我们直接设一个二维数组a[101][101](大小可以自由设置,不过要比输入的字符串长度大),用第一维度来表示字符串S的每一个字符之前的这一段字符串处理需要的最小计数值,第二维度来表示字符串T的。(类比到斐波那契数列的动态规划法就是a[2]对于f(2),f(2)保存的就是f(1)+f(1)的值,同理)
此题有三种大情况:均一一对应的比较(即倒数第一个与倒数第一个比,倒数第二个与倒数第二个比);比较的字符不相等,s 的前一个和t的当前比较(意味着s要删除最后一个);t的前一个和s的当前比较(意味着t要删除最后一个)。(插入是一样的)
核心代码如下(注释内已给出与三句话对应的地方):
#include<iostream>
using namespace std;
int a[101][101];
int min(int a,int b,int c) //取优函数(比较函数)
{
if(a>=b&&c>=b)
return b;
else if(a<=b&&a<=c)
return a;
else if(c<=b&&c<=a)
return c;
}
int com(string s,string t)
{
/*
* 取字符串s的长度(因为每次调用可能会比之前短,因为要从
* 最后一个字符开始比较,要么指向t的最后一位向前移,要么指向s的最后一位向前移)
*/
int ns=s.length();
int nt=t.length();
if(a[ns][nt]!=-1) //第一句话,如果已经计算过了,则直接返回来用
return a[ns][nt];
/*
* 第二句话,如果分解回了起点(这里是比较到达了两字符串的开头),则对起点进行
* 情况分析,并且保留值(类比到斐波那契数列里就是保存f(1)的值)
*/
if(ns==1)
{
if(s[0]!=t[nt-1]) //如果是到达了s的开头,并且开头与此时的t的当前位置不同
{
a[1][nt]=nt;
}
else //如果相同
a[1][nt]=nt-1;
return a[1][nt];
}
else if(nt==1) //同理上面,还是第2句话
{
if(t[0]!=s[ns-1])
{
a[ns][1]=ns;
}
else
a[ns][1]=ns-1;
return a[ns][1];
}
/*
*第3句话,若为第一次调用此函数,则a[ns][nt]即为所求的结果
*若是中间过程的调用,则每次调用都取再往前分解后的值的最优解(这里的min函数是取每次过程的最小值)
*通俗讲就是假设此次过程是父过程,再分解是子过程,则每一个父过程都取子过程的最优解(这里的取优即min函数操作)
*/
else
{
a[ns][nt]=min(com(s.substr(0,ns-1),t.substr(0,nt-1))+(t[nt-1]==s[ns-1]?0:1),com(s,t.substr(0,nt-1))+1,com(s.substr(0,ns-1),t)+1);
return a[ns][nt];
}
}
int main(){
string s,t;
cin>>s>>t;
for(int i=0;i<s.length()+1;++i) //初始化存值数组
for(int j=0;j<t.length()+1;++j)
a[i][j]=-1;
com(s,t);
cout<<a[s.length()][t.length()]<<endl; //所求结果
return 0;
}
只要按照三句话的顺序来思考,就可以解决问题。
文章为博主自学记录,如有错误,欢迎指正。