求解动态规划问题的核心在于找准阶段与阶段之间的状态变化规律,从而制定状态转化的决策,由上一阶段的所有局部最优状态值推出下一阶段的局部最优,进而推出全局最优。 在求解问题的时候,我们通常要构造一个二维数组,用来保存当前得到的局部最优值,这样在推导下一阶段的最优值的时候就可以利用上一阶段得到的结果求解,而这显然比穷举更高效。 记住动态规划的一个思想:许多当前的局部最优看不见未来的最优,但是未来的最优一定包含当前的某个局部最优。
以某年的信息学竞赛为例,题目大意如下:
在一个长为N的数字字符串s中插入r个乘号,将s拆分成r+1部分,求其最大乘积。
思路:
该题目可以用动态规划的思想求解,因为s的最大乘积可以由局部最大乘积得到。如果我们把该题的阶段定义为插入乘号的个数,那么阶段1就是插入第1个乘号,阶段二就是插入第2个乘号,...,阶段r就是插入第r个乘号。
而每个阶段的状态则对应插入乘号的位置。另外,每个阶段的取值集合由该阶段的乘号数目决定。例如,阶段1对应的字符串长度显然应该大于1,阶段r对应的字符串长度应该大于r+1。
有了阶段的制定,进一步分析得到其状态转化的决策:
我们设阶段i的状态是f[i,k],其中i是目前数字字符串的结尾,即s.sub(0,i),k是待插入的第k个乘号。基于前面提到得到未来的最优一定包含当前的某个局部最优,这里的未来就是f[i,k].它的最优值必定从当前已得到的最优解集合{f[j,k-1]}(其中k<=j<r)得到。{f[j,k-1]}表示数字字符串s.sub(0,j)中插入k-1个乘号的最大值。
经过分析,状态转移公式如下:
f[i,k]=max{f[[j][k-1]*Int(s.substring(j,i))} 其中(j>=k&&j<i)
有了状态转移公式,那么编程就比较容易了。
Java代码
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class MaxMultiplication {
/**
* 在一个长为N的数字串中插入r个乘号,将它拆分成r+1部分,求其最大乘积
* @param integerNum: 待拆分的自然数
* @param mSignNum:待插入的乘号数
* @return 最大乘积
*/
public static long handleMaximumMultiplicationWithMultipleSign(String integerNum, int mSignNum)
{
if(integerNum==null||mSignNum<0||mSignNum>=integerNum.length())
throw new IllegalArgumentException("Wrong Arguments Input!");
boolean result = isNaturalNumber(integerNum);
if(!result)
throw new IllegalArgumentException("Please input a natural number!");
int numLength=integerNum.length();
int[][] dpMultiTable=new int[numLength+1][mSignNum+1];//动态规划二维表
for(int t=1;t<=numLength;t++)//初始化动态规划二维表:填入插入0个乘号的值
{
dpMultiTable[t][0]=Integer.parseInt(integerNum.substring(0,t));
}
for(int r=1;r<=mSignNum;r++)//填充动态规划二维表(乘号数目是一维:x)
{
for(int i=r+1; i<=numLength;i++)//构造动态规划二维表(当前扫描字符串是一维:y)
{
//状态遍历(从r-1个乘号的状态到r个乘号的状态),寻找当前f(i,r)的最优解并记录
//j是插入第r个乘号的可能位置
int current_max=0;
for(int j=r;j<i;j++)
{
int currentvalue=dpMultiTable[j][r-1]*Integer.parseInt(integerNum.substring(j,i));
if(current_value>current_max)
{
dpMultiTable[i][r]=current_value;
current_max=current_value;
}
}
}
}
return dpMultiTable[numLength][mSignNum];
}
/**
* 判断输入是否是自然数
* @param integerNum
* @return
*/
public static boolean isNaturalNumber(String integerNum) {
String reg = "^[1-9](\\d*)$";
Pattern p = Pattern.compile(reg);
Matcher m = p.matcher(integerNum);
boolean result = m.find();
return result;
}
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(handleMaximumMultiplicationWithMultipleSign("12345",2));
System.out.println(handleMaximumMultiplicationWithMultipleSign("10",0));
}
}