【动态规划】记忆搜索(C++)

  前几天还在踟蹰我应该注重培养作项目的能力仍是修炼算法以及数据结构,而后发现这个场景有点似曾相识。曾几什么时候的一个月里,我有三件比较重要的事情要解决,在那个月刚开始的时候我一直在想我应该从那件事情开始着手,甚至在脑海里给这三件事情的重要性排个序,思绪争执不下,烦躁之极,不如玩玩游戏散散心吧,或许等下就知道怎么作了......结果一个月下来,没有一件事的结果能使我十分满意。作项目的编程能力当然须要学习,但技术更替这么快,亘古不变的是那些算法的思惟和设计的数据结构,既然决定要学,就踏踏实实从算法方面入手吧。算法

  前些日子了解了滚动数组的求解,这几天学习了计划搜索。什么是记忆搜索,就是你在当前step所作的决定影响到了你接下来的每一个决定,你必须考虑下一步的结果,使最后的价值最大化。如下经过两个例子来详细分析一下。(鄙人以为,学习算法的初步就是学习例题,刚开始的时候不要急于从例题当中提取那些比较深度的思惟,只管学习,当火候到了,天然就触类旁通,固然这其中的兴趣成分十分重要。就像那个谁名人来的说过:我读诗,并不刻意去理解这句诗是什么意思,我只是喜欢她,我只是不断去读她,直到有一天,咱们相互理解)编程

1.Coins in a line : http://www.lintcode.com/en/problem/coins-in-a-line/数组

2.Coins in a line II : http://www.lintcode.com/en/problem/coins-in-a-line-ii/数据结构

对于第一题,大概题意是这样的:给你一排硬币,两个足够聪明的人交替从头开始取这些硬币(两我的必须足够聪明,假设是爱因斯坦和特斯拉吧^ ^),每次能够取一或两个,当取走最后哪一个硬币的人获胜,给你n个硬币,问先手取的人可否获胜。学习

按照解动态规划的步骤来测试

  一、状态(State):当目前有n个硬币,我要怎么取才能保证我能取得最后一枚硬币,咱们往前推一步,n个硬币当中,我能够取一枚或者两枚,当你这一步取完的时候,对手也会执行一样的动做(足够聪明的对手也会考虑怎么取才能取到最后一枚),咱们用布尔类型dp[x]来表示当目前剩下x枚硬币且轮到你来取硬币的时候能不能赢。这个时候咱们分两种状况讨论:spa

①我取一枚,对手能够取一枚或者两枚,因此到下一轮到我取硬币的时候,标示你可否获胜的状态就是dp[x-2]或dp[x-3](对手取一枚或者两枚)。设计

②我取两枚,对手能够取一枚或者两枚,到下一轮到我取硬币的时候,标志你可否获胜的状态就是dp[x-3]或dp[x-4]。3d

那也就是说,若是dp[x-2]和dp[x-3]都是true(获胜)的话,那我在x这一步确定取一枚,这样就能保证在dp[x]的时候为true。为何dp[x-2]和dp[x-3]要同时为true才行,由于你取一枚以后,对手取完再轮到你的时候决定你是取dp[x-2]和dp[x-3]的状况是由对手决定的,若是其中有一个为false,那对手确定不会让另外一个true发生。反之也成立,若是dp[x-3]和dp[x-4]同时为true的话,那我在x这一步的时候确定取两枚,这样对手就不管取一枚或者两枚都没法阻止我获胜啦嘿。code

                            

如示意图中,假设当前剩下四枚硬币,若是我取一枚,接下来对手面对三枚,怎么取都能让下一步的我取到最后一枚硬币,但若是我取两枚,那对手足够聪明,确定会两枚都取而获胜,因此我在面对四枚硬币的时候确定取一枚。此处就至关于肯定,若是是我面对四枚硬币,那我确定能赢,因此往上推,我只须要考虑能不能构造剩下四枚硬币的结果就能够了。

  二、方程(Function):状态肯定了,方程也随之可以肯定下来。不可贵出,当剩下n枚硬币,此时先手的人可否获胜为dp[n], 有

  dp[n] = MemorySearch(n-2) && MemorySearch(n-3) || MemorySearch(n-3) && MemorySearch(n-4); 

  三、初态(Initialization):轮到我取硬币,若是已经没有了(n=0),那说明对手赢了,若是剩下一枚或者两枚,那我能赢,剩下三枚,对手赢,剩下四枚,我赢。所以有

   if(0 == n || 3 == n) dp[n] = false; if(1 == n|| 2 == n||4 == n) dp[n] =true 

  四、结果(Result): dp[n] (面对n枚硬币先手)

 代码实现以下:传入n枚硬币做为参数,返回是否可以得到胜利。

bool firstWillWin(int n) {
        int dp[n+1];
        bool flag[n+1] = {false};
        return MemorySearch(dp,n,flag);
    }
    bool MemorySearch(int *dp,int n,bool *flag){
        if(true == flag[n])
            return dp[n];
        flag[n] = true;
        if(0 == n||3 == n) dp[n] = false;
        else if(1 == n||2==n||4==n||5==n) dp[n]= true;
        else{
            dp[n] = ( MemorySearch(dp,n-2,flag) && MemorySearch(dp,n-3,flag) )|| ( MemorySearch(dp,n-3,flag)&&MemorySearch(dp,n-4,flag) );
        }
        return dp[n];
    }

以上是根据普通记忆搜索算法的思路简单明了容易理解,但此题还能够用数组状态推移的方法解。肯定初态以后,之后的每一个f[x]的状态均可以由f[x-2],f[x-3],f[x-4]得出,所以有

bool firstWillWin(int n) {
        bool f[n];
        if(1 == n ||2==n||4 == n||5 == n) return true;
        if(3 == n || 0==n) return false;
        f[0] = true;
        f[1] = true;
        f[2] = false;
        f[3] = true;
        f[4] = true;
        for(int i=5;i<n;i++)
            f[i] = (f[i-2] && f[i-3]) || (f[i-3] && f[i-4]);
        return f[n-1];
}

此处f[x]表示面对的硬币数为x+1枚。最后的结果为f[n-1](即n枚硬币的状况)。

到此,解决了给你一堆硬币轮流取,到底会不会赢的状况。接下来考虑一下一道拓展的题目,看第二个问题:Coins in a line II

题意大概是:给你一堆不一样价值的硬币,取硬币的规则跟前者同样,取完硬币的时候得到价值总值高的一方胜利,问先手取的人可否获胜。

在这种状况下,就不是取到最后一个硬币就可以赢得,每一步我取一枚或者两枚都好,只要硬币取完拥有的价值量最高。那好,当硬币少于或者等于3枚的时候先手,那没有疑问,取最大值价值量最高(1枚的时候取1枚,2枚或3枚的时候取2枚),固然这个时候轮到对手的时候也会这么取,但当有四枚硬币的时候怎么取。举个栗子,当前为【1,2,4,8】的时候,我取一个,价值为【1】,剩下【2,4,8】,通过前面的推导,剩下三枚取两枚,结果被取走{2,4},我取最后一个【8】,这个时候价值总量为【9】。若是我取两个,价值为【3(1+2)】,剩下【4,8】,必须被对手取走,此时价值总量为【3】,比前一种状况的价值量低,因此在剩下四枚硬币并且此时我先手的话,确定能根据这种方法来判断我到底取一枚仍是两枚使最后得到的价值量最高。用dp[x]来表示当前剩下x枚硬币的时候取到最后能取得的最大价值量,当前我取一个,下一轮我面对的价值量就是f[x-2]或f[x-3],由于对手足够聪明,因此他确定会根据f[x-2]和f[x-3]的价值量来决定当他面对x-1个硬币的时候是取一枚仍是两枚,此时的话 dp[x] = min(dp[x-2],dp[x-3]) + values[x] ,此为面对x枚硬币取一枚的状况。第二种状况我取两枚,下一轮我面对的价值量是f[x-3]或f[x-4],一样的道理,但最后 dp[x] = min(f[x-3],f[x-4]) + values[x] + values[x+1] ,因此我到底取一枚仍是两枚,价值量是为 dp[x] = max( min(dp[x-2],dp[x-3])+values[x],min(dp[x-3],dp[x-4])+values[x]+values[x+1]) ,往上推一样的道理,dp[x]的最大值取决于dp[x-2],dp[x-3],dp[x-4]的值,最后能够得出dp[n]就是面对n枚硬币的时候先手能够取得最大的价值量,此时只要判断dp[n] > sum/2便可胜出,sum为全部硬币的总共价值量。

代码以下:

bool firstWillWin(vector<int> &values) {
        int sum = 0;
        int dp[values.size()+1];
        bool flag[values.size()+1];
        for(int i=0;i<values.size()+1;i++) flag[i] = false;
        for(vector<int>::iterator ite = values.begin();ite!=values.end();ite++)
            sum += *ite;
        return sum/2<MemorySearch(dp,values.size(),flag,values);
    }
int MemorySearch(int* dp,int n,bool *flag,vector<int> values){
        if(flag[n])
            return dp[n];
        flag[n] = true;
        int count = values.size();
        if(0 == n) dp[n] = 0;
        else if(1 == n) dp[n] = values[count-n];
        else if(2 == n||3 == n) dp[n] = values[count-n] + values[count-n+1];
        else{
            dp[n] = max( min(MemorySearch(dp,n-2,flag,values),MemorySearch(dp,n-3,flag,values))+ values[count-n],
                             min(MemorySearch(dp,n-3,flag,values),MemorySearch(dp,n-4,flag,values))+values[count-n]+values[count-n+1] );
        }
        return dp[n];
}

除了这个方法,还有相似第一个题目第二种解法的方法,此处留给读者本身实现。

以上为我的对记忆搜索算法的求解过程的理解,每一句代码均通过本人测试可用,若有问题,但愿你们提出斧正。

 

 

尊重知识产权,转载引用请通知做者并注明出处!

相关文章
相关标签/搜索