Java算法学习:蓝桥杯——地宫寻宝(DFS+动态规划—记忆型递归)

Java算法学习:蓝桥杯——地宫寻宝(DFS✖记忆型递归)

题目:

标题:地宫取宝

    X 国王有一个地宫宝库。是 n x m 个格子的矩阵。每个格子放一件宝贝。每个宝贝贴着价值标签。

    地宫的入口在左上角,出口在右下角。

    小明被带到地宫的入口,国王要求他只能向右或向下行走。

    走过某个格子时,如果那个格子中的宝贝价值比小明手中任意宝贝价值都大,小明就可以拿起它(当然,也可以不拿)。

    当小明走到出口时,如果他手中的宝贝恰好是k件,则这些宝贝就可以送给小明。

    请你帮小明算一算,在给定的局面下,他有多少种不同的行动方案能获得这k件宝贝。

【数据格式】

    输入一行3个整数,用空格分开:n m k (1<=n,m<=50, 1<=k<=12)

    接下来有 n 行数据,每行有 m 个整数 Ci (0<=Ci<=12)代表这个格子上的宝物的价值

    要求输出一个整数,表示正好取k个宝贝的行动方案数。该数字可能很大,输出它对 1000000007 取模的结果。

例如,输入:
2 2 2
1 2
2 1
程序应该输出:
2

再例如,输入:
2 3 2
1 2 3
2 1 5
程序应该输出:
14

资源约定:
峰值内存消耗(含虚拟机) < 256M
CPU消耗  < 2000ms


请严格按要求输出,不要画蛇添足地打印类似:“请您输入...” 的多余内容。

所有代码放在同一个源文件中,调试通过后,拷贝提交该源码。
注意:不要使用package语句。不要使用jdk1.7及以上版本的特性。
注意:主类的名字必须是:Main,否则按无效代码处理。

思路:

首先看到这个矩阵型+只能向右/向下的搜索,首先想到类似“2013年振兴中华”这一题的dfs解法,那么就按照这一个思路去解决这个问题。

首先要有一个主函数,和一个dfs函数,dfs函数的参数很重要,分别设定为。x坐标,y坐标,max当前最大值,和cnt作为计算宝物数量的计数器变量,用来判断是否拿满了k件宝物。

当小明走到某一步的时候,如果当前取出的宝物比k大,就不在搜索这条分支,return 0;

dfs里面要注意的是,一定要有:

  1. 当前格子的价值比max的值大然后向右走/向下走并且拿起宝物
  2. 当前格子的价值比max的值 大或者小但是向右走/向下走但是不拿起宝物

这样四种情况的递归调用。

当走到最右下角的时候,这时候有两种情况,一个是取满了k件宝物,可以return了,另外一种是还差一件取满k件宝物,并且最后一格上面放着的宝物比当前已有的max还大,就再取出一件宝物来。

*为了方便我没有输入数据,只是把样例数据写死在代码里面

代码实现具体:


    private static final int MOD = 1000000007;
    private static int n;
    private static int m;
    private static int k;
    private static long ans = 0;

    private static int[][] table = {
            {1,2,3},{2,1,5}
    };

    public static void main(String[] args) {
        //行
        n=2;
        //列
        m=3;
        //需要的宝物数量
        k=2;
        //缓存数组初始化为-1
        ans = dfs(0,0,-1,0);
        System.out.println(ans);
    }

    /**
     * @param x
     * @param y
     * @param max
     * @param cnt
     * @return
     */
    private static long dfs(int x,int y,int max,int cnt){
        //溢出防御,递归出口
        if(x==n||y==m||cnt>k){
            return 0;
        }

        int value = table[x][y];
        long ans = 0;
        //走到最后一个格子前,递归出口
        if(x==n-1&&y==m-1){
            //一种情况是已经取满了k件宝物 另外一种情况是还差一件,但是最后一件的价值正好大于max
            if(cnt==k||(cnt==k-1&&value>max)){
                return 1;
            }
            return ans;
        }

        //如果当前格子的价值比最大值大,取出的情况
        if(value>max){
            //取出这个物品
            ans+=dfs(x,y+1,value,cnt+1);
            ans+=dfs(x+1,y,value,cnt+1);
        }

        //价值小或者价值大,但是不取出的情况
        ans+=dfs(x,y+1,max,cnt);
        ans+=dfs(x+1,y,max,cnt); 
        return ans%MOD;
    }

上述代码都依照dfs深度搜索的基本题型进行书写即可,难点就在一跳出条件很复杂,递归的情况分支也很复杂。

并且这样做,递归的效率很低。当数据量比较大的时候,run程序的时间就比较长,这样给分就不高。

改进:

针对上面说的这种问题,我们观察题目,由于这个问题中,DFS函数的参数是有可能相同的。

比如中间的任意一个格子,可以是来自多个方向,并且从每个方向过来的小明,身上携带的宝物最大价值和宝物数量都有可能一样。

上面这种情况就是所谓的存在着重复子问题。这些具有相同参数的dfs函数在之前的代码里就需要反复的去计算,这对计算机的内存和运行时间都是极大的浪费。因此采用动态规划记忆型递归的方法来的方式来解决。

所谓记忆型递归就是吧相同参数的dfs函数的结果用一个长度为(i*j*n*m)的数组保存,ijnm分别是dfs参数可能取到的范围,比如题目中就有提示,这个记忆数组的长度就是51*51*14*14。当我们在这个数组中保存结果的时候,每一次调用dfs都从中查找相应的单元是否存在着之前记录下来的答案。如果有,就返回,不用多次的去调用求解。这样就节省了cpu资源和时间。

改进后的代码实现:

public class _地宫取宝记忆型递归 {

    private static final int MOD = 1000000007;
    private static int n;
    private static int m;
    private static int k;
    private static long ans = 0;
    //存储每次递归的结果
    private static long[][][][] cache = new long[51][51][14][14];

    private static int[][] table = {
            {1,2,3},{2,1,5}
    };

    public static void main(String[] args) {
        //行
        n=2;
        //列
        m=3;
        //需要的宝物数量
        k=2;
        //缓存数组初始化为-1
        for (int i = 0; i <51 ; i++) {
            for (int j = 0; j < 51; j++) {
                for (int l = 0; l < 14; l++) {
                    for (int o = 0; o < 14; o++) {
                        cache[i][j][l][o] = -1;
                    }
                }
            }
        }
        ans = dfs(0,0,-1,0);
        System.out.println(ans);
    }

    /**
     * @param x
     * @param y
     * @param max
     * @param cnt
     * @return
     */
    private static long dfs(int x,int y,int max,int cnt){
        //查询缓存是否有记录,若有,返回记录
        if(cache[x][y][max+1][cnt]!=-1){
            return cache[x][y][max+1][cnt];
        }

        //溢出防御,递归出口
        if(x==n||y==m||cnt>k){
            return 0;
        }

        int value = table[x][y];
        long ans = 0;
        //走到最后一个格子前,递归出口
        if(x==n-1&&y==m-1){
            //一种情况是已经取满了k件宝物 另外一种情况是还差一件,但是最后一件的价值正好大于max
            if(cnt==k||(cnt==k-1&&value>max)){
                return 1;
            }
            return ans;
        }

        //如果当前格子的价值比最大值大,取出的情况
        if(value>max){
            //取出这个物品
            ans+=dfs(x,y+1,value,cnt+1);
            ans+=dfs(x+1,y,value,cnt+1);
        }

        //价值小或者价值大,但是不取出的情况
        ans+=dfs(x,y+1,max,cnt);
        ans+=dfs(x+1,y,max,cnt);

        //写缓存

        cache[x][y][value][cnt] = ans%MOD;
        return ans%MOD;
    }
}

 

总结:第一次接触到DP,以后会好好学习和理解动态规划这个知识点。

 

你可能感兴趣的:(数据结构与算法,Java)