可以状态穷举的典型例子:
给定一个数组,它的第i个元素是一支给定的股票在第i天的价格。
设计一个算法来计算你所能获取的最大利润。你最多可以完成k笔交易(买了卖掉)。
注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。
示例:
输入:[3,2,6,5,0,3],k=2
输出:7=-2+6-0+3
思路:如果不考虑某一天买入后,后面只能观望或卖出,不能再次买入的要求,那么每天都能有三种选择:买入、卖出(如果有买的话)、观望;穷举所有可能的组合,比较每一种组合的最终交易结果,取最大值就是能获取的最大利润了。这就是状态机穷举的思想。
与此相似的题是给定整数数组[1,3,1,4,5]让在每个整数前加+或-号得到的表达式结果是6的组合数。暴力解法的话用状态机穷举总是可以。
而状态机穷举有两个思路:
1、每一个位置的每一种选择都只知道自己对所在的当前分支的结果值的影响,需要将每个分支从末尾已知边界值上溯返回给上层调用的每个选择,然后上层调用对其每个选择择优并叠加自身计算结果后,继续返回给上上层调用,,,直至入口调用的每个选择都返回其最优解,那么入口调用对其每个选择择优并叠加自身计算结果返回,即是最终最优解。
/*
状态穷举,每个状态的选择可能影响其后分支上状态的选择,当前状态的选择只确定当前步的计算结果,需要当前
状态的所有子分支返回一个最优解,然后当前选择与当前层其他选择进行最优解并返回给上层选择。
与回溯法一样,每条分支都是在分支最底层将分支结果计算出来,回溯法会在底层计算出分支结果时更新线程最优解,
然后回退状态到当前层下一个选择,下一个选择也是在底层,计算出分支结果时更新线程最优解。
状态穷举:
1、可以将上层选择的计算结果传值到下层选择,在递归的底层分支计算出当前分支的结果后更新线程最优解。
2、或者将底层分支计算出的最后单步结果返回到上层,上层对其分支上各自返回的结果进行最优后继续返回
上上层,上上层对其分支上各自返回的结果进行最优后继续返回上上上层。
*/
//底层最优化回溯返回上层
int maxEarnViolateStateUp(const vector<int> & prices,int iday,int times,bool bbuy){ //入口调用bbuy=true,iday=0
if(iday>=prices.size())return 0; //时间到头了,后面不会有任何交易结果了
//状态机(选择)穷举每时间单位的股价,而不是穷举遍历前次交易(见上),在回溯中返回当前阶段所有分支所能获得的最大利润并继续回溯返回
//状态机选择穷举在当前步只计算当前步的各种选择的结果值,当前分支的最优解依赖于后续小规模数组,并且规模小到边界时边界值确定,从
//边界值上溯将底层分支的解汇总得到最优解,再上溯到次底层分支的解汇总得最优解,,,层层上溯到顶层分支的解汇总得最优解
int resbuy(-1*(1<<30)),ressel(-1*(1<<30)),ressilent(-1*(1<<30));
//当前时间不作为
ressilent=maxEarnViolateStateUp(prices,iday+1,times,bbuy);
if(bbuy && times>0){
//当前时间买,在买股票后递减剩余交易数,不管是否休市卖不出去,穷举么,如果买了卖不出去则该分支肯定利润低,不会影响最高利润判定
resbuy=-prices[iday]+maxEarnViolateStateUp(prices,iday+1,times-1,false);
return max(resbuy,ressilent); //买或不作为
}else if(bbuy && times<=0){
resbuy=0; //当前分支不会再有任何交易结果了
return 0; //要买但是没有交易机会了,只能不作为,resbuy与不作为的ressilent一样,后面的交易结果都是0
// return max(resbuy,ressilent);
}
if(!bbuy){
//当前时间卖
ressel=+prices[iday]+maxEarnViolateStateUp(prices,iday+1,times,true);
return max(ressel,ressilent); //卖或不作为
}
std::cout<<"error! not expected occur!\n";
return 0;
}
2、从入口调用出发,将入口调用处每个选择的单步结果传递到分支,,,一直传递到分支底层边界值已知时,当前分支的结果已知,将其与全局最优解对比,更新全局最优解
//上层将其计算结果传递给底层,由底层收敛后计算出分支结果更新到线程结果
void maxEarnViolateStateDown(const vector<int> &prices,int iday,int times,bool bbuy,int pathres,int &res){
//传入res用于保存全局最优解,传入pathres用于保存分支路径累计结果,初始0
if(iday>=prices.size()){ //分支结束,更新状态
if(pathres>res) res=pathres;
return;
}
if(bbuy && times>0){ //只当买入时才递减交易次数,状态传递到分支
maxEarnViolateStateDown(prices,iday+1,times-1,false,pathres-prices[iday],res);
//买或不作为
}
if(bbuy && times<=0){ //要买但没机会了,后面也不会有任何影响结果的选择,该选择没有有效分支了,该选择可以结束了
if(pathres>res) res=pathres;
return; //相当于不作为,可以返回了
}
if(!bbuy){ //卖,卖总是可以的
maxEarnViolateStateDown(prices,iday+1,times,true,pathres+prices[iday],res);
//卖或不作为
}
//不作为的选择
maxEarnViolateStateDown(prices,iday+1,times,bbuy,pathres,res);
return;
}