从暴力递归到动态规划

动态规划就是将暴力递归中重复的结果记录在DP表中,从而提高效率,就是用空间换时间。

1.1. 斐波那契数列暴力递归解法

    public static int f(int n) {
        if (n == 1) {//N为1时,返回1
            return 1;
        }
        if (n == 2) {//N为2时,返回1
            return 1;
        }
        //否则返回:
        return f(n - 1) + f(n - 2);
    }

这样算的过程中有很多重复计算的结果,大大降低运行效率。如果将计算过的记录存进缓存就可以大大节约运行时间。就要用到动态规划。

1.2.斐波那契数列动态规划解法

    public static int f(int n) {
        if (n <= 1) {
            return n;
        }
        int[] dp = new int[n + 1];
        dp[0] = 0;
        dp[1] = 1;
        for (int i = 2; i <= n; i++) {
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }

2.1. 题目:

假设有排成一行的N个位置,记为1~N, N-定大于或等于2,
开始时机器人在其中start的位置上(start -定是1~N中的- -个)
规定机器人必须走rest步,最终能来到aim位置(rest也是1~N中的一个)的方法有多少种
给定四个参数start ,rest,aim,N返回方法数。

2.2. 暴力递归解法

分析:

如果机器人来到1位置,那么下一步只能往右来到2位置;
如果机器人来到N位置,那么下一步只能往左来到N-1位置;
如果机器人来到中间位置,那么下一步可以往左走或者往右走;

    //暴力解法
    //start为起始位置,rest为还有多少步结束,aim为目标位置,N为总长度
    public static int f1(int start, int rest, int aim, int N) {
        if (rest == 0) {//还有0步
            return start == aim ? 1 : 0; //如果起始位置在目标位置返回1,否则0;
        }
        if (start == 1) { //如果起始位置在1 ,下一步只能走2
            return f1(2, rest - 1, aim, N);
        }
        if (start == N) { //如果起始位置在N ,下一步只能走N-1
            return f1(N - 1, rest - 1, aim, N);
        }
        //起始位置在中间
        return f1(start - 1, rest - 1, aim, N) + f1(start + 1, rest - 1, aim, N);
    }

2.3第一次优化

有2个可变参数影响结果。start和rest

start范围时1~N
rest范围时0~rest
所以建一个2维表,把所有可能都装下,把二维表全部初始为-1。

    public static int f2(int start, int rest, int aim, int N, int dp[][]) {
        //之前算过
        if (dp[start][rest] != -1) {
            return dp[start][rest];
        }
        //之前没算过
        int ans = 0;

        if (rest == 0) {//还有0步
            ans = start == aim ? 1 : 0; //如果起始位置在目标位置返回1,否则0;
        } else if (start == 1) {//如果起始位置在1 ,下一步只能走2
            ans = f2(2, rest - 1, aim, N, dp);
        } else if (start == N) { //如果起始位置在N ,下一步只能走N-1
            ans = f2(N - 1, rest - 1, aim, N, dp);
        } else
            //起始位置在中间
            ans = f2(start - 1, rest - 1, aim, N, dp) + f2(start + 1, rest - 1, aim, N, dp);

        dp[start][rest] = ans;
        return ans;
    }



    public static int way2(int start, int rest, int aim, int N) {
        int dp[][] = new int[N + 1][rest + 1];
        for (int i = 0; i <= N; i++) {
            for (int j = 0; j <= rest; j++) {
                dp[i][j] = -1;
            }
        }


        return f2(start, rest, aim, N, dp);
    }

2.4 最后优化

最后优化就是一个填表过程,根据暴力递归的过程来填表。

行为起始位置star,列为还剩多少步rest。第一列只有aim位置为1,其余全部为0,这样第一列就填好了,第一行没有用,从第二行开始,第二行的数据是依赖箭头所指的位置的值,最后一行的数据是依赖箭头所指的值。 其余中间位置的值是也是依赖箭头所指的值(第二张图)。

接下来的代码就是一个填表的过程。

    //最终解法
    public static int way3(int start, int rest, int aim, int N) {
        int dp[][] = new int[N + 1][rest + 1];
        dp[aim][0] = 1; //第1列其他位置为 0
        for (int r = 1; r <= rest; r++) {
            dp[1][r] = dp[2][r - 1];
            for (int c = 2; c < N; c++) {
                dp[c][r] = dp[c - 1][r - 1] + dp[c + 1][r - 1];
            }
            dp[N][r] = dp[N - 1][r - 1];
        }
        return dp[start][rest];
    }

3.1  题目


给定-个整型数组arr,代表数值不同的纸牌排成一条线
玩家A和玩家B依次拿走每张纸牌
规定玩家A先拿,玩家B后拿
但是每个玩家每次只能拿走最左或最右的纸牌
玩家A和玩家B都绝顶聪明
请返回最后获胜者的分数。

3.2 暴力递归
 

分析:

     1.先手函数:

     当L==R时,只有一张牌,返回的就是L或者R的值;

      L!=S时,返回先拿走右边牌的值加上去掉这张牌的后手函数的值:arr[L] + befor(arr, L + 1, R); 

              或者返回先拿走左边牌的值加上去掉这张牌的后手函数的值 :arr[R] + befor(arr, L, R - 1);

             返回其中最大值;

    2.后手函数:

     当L==R时,返回0;

     L!=S时,返回少左边牌之后的先手函数返回的值:first(arr, L + 1, R),

                    或者,返回少右边牌之后的先手函数返回的值:first(arr, L, R - 1);

                    返回其中最小值,因为先手会把最好的拿走,留下最小的给后手;

    //先手能得到的最大分数
    public static int first(int[] arr, int L, int R) {
        if (L == R) {
            return arr[L];
        }
        //先手取得一张牌后就相当于变成减一张牌的后手,就是加上减一张牌的后手函数
        int p1 = arr[L] + befor(arr, L + 1, R);
        int p2 = arr[R] + befor(arr, L, R - 1);
        return Math.max(p1, p2);
    }

    //后手只能得到先手过后的最小值,所以是MIN
    public static int befor(int arr[], int L, int R) {
        if (R == L) {
            return 0;
        }
        int b1 = first(arr, L + 1, R);
        int b2 = first(arr, L, R - 1);
        return Math.min(b1, b2);
    }

    //返回先手或后手的最大值
    public static int f(int arr[]) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        int N = arr.length;

        int first = first(arr, 0, arr.length-1);
        int befor = befor(arr, 0, arr.length-1);
        return Math.max(first, befor);
    }

3.3  加缓存法

先手函数加一张缓存表 fgame[L][R],后手函数加一张缓存表,bgame[L][R],表中的数据都初始化为-1,进入先手后手函数前先判断是否值为-1,不为-1直接返回值,否则往下走,过程与暴力递归很相似。

public static int first(int arr[], int L, int R, int fgame[][], int bgame[][]) {
        if (fgame[L][R] != -1) {
            return fgame[L][R];
        }
        int ans = 0;
        if (L == R) {
            return arr[L];
        } else {
            int f1 = arr[L] + before(arr, L + 1, R, fgame, bgame);
            int f2 = arr[R] + before(arr, L, R - 1, fgame, bgame);
            ans = Math.max(f1, f2);
        }
        fgame[L][R] = ans;
        return ans;
    }

    private static int before(int arr[], int L, int R, int fgame[][], int bgame[][]) {
        if (bgame[L][R] != -1) {
            return bgame[L][R];
        }
        int ans = 0;

        if (L != R) {
            int b1 = first(arr, L + 1, R, fgame, bgame);
            int b2 = first(arr, L, R - 1, fgame, bgame);
            ans = Math.min(b1, b2);

        }
        bgame[L][R] = ans;
        return ans;
    }

    public static int f2(int[] arr) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        int N = arr.length;
        int fgame[][] = new int[N][N];
        int bgame[][] = new int[N][N];
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                fgame[i][j] = -1;
                bgame[i][j] = -1;
            }
        }
        int first = first(arr, 0, arr.length - 1, fgame, bgame);
        int before = before(arr, 0, arr.length - 1, fgame, bgame);

        return Math.max(first, before);

    }

3.4 动态规划法

       给先手后手都创建一张表,打X的地方是不合法的,用不到。然后根据暴力递归来推出其他格子的数据,F表中的?依赖于G表中对应位置(R-1,L)和(R,L-1)这2个格子的值,同理G表中的?位置也是依赖F表中对应位置(R-1,L)和(R,L-1)这2个格子的值。最终我们返回的是(0,N-1)这个位置的值,这也是从暴力递归中得出的。

    public static int f3(int arr[]) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        int N = arr.length;
        int fgame[][] = new int[N][N];
        int bgame[][] = new int[N][N];

        for (int i = 0; i < N; i++) {
            fgame[i][i] = arr[i];
        }
        for (int S = 1; S < N; S++) {
            int L = 0;
            int R = S;
            while (R<N) {

                fgame[L][R] = Math.max(arr[L] + bgame[L + 1][R], arr[R] + bgame[L][R - 1]);
                bgame[L][R] = Math.min(fgame[L + 1][R], fgame[L][R - 1]);

                L++;
                R++;
            }
        }
        return Math.max(fgame[0][N - 1], bgame[0][N - 1]);
    }

 这个循环中表示当L==R时,在表中就表示给对角线赋值,因为G表中对角线初始值为0,所以不用赋值。

 

这个for中表示沿着这条线依次赋值,直到星星位置。S为列,L为行。

4.1 背包问题(01)

​ 题目:有W件物品和一个容量为bag的背包。第 index件物品的费用(即体积,下同)是  ,价值是  。求解将哪些物品装入背包可使这些物品的费用总和不超过背包容量,且价值总和最大。

4.2 暴力递归解法

    //w为物品重量,
    // v为物品价值,
    // index为考虑到了这个物品 要还是不要,
    // bag为背包容量
    public static int process(int w[], int v[], int index, int bag) {
        if (bag < 0) {
            return -1;
        }
        if (index == w.length) {
            return 0;
        }
        //不要这个物品
        int p1 = process(w, v, index + 1, bag);

        //要这个物品,因为考虑到如果要了这件物品可以会超重,所以判断一下
        int next = process(w, v, index + 1, bag - w[index]);
        int p2 = 0;
        if (next != -1) {
            p2 = v[index] + next;
        }

        return Math.max(p1, p2);
    }

4.3 动态规划解法

动态规划解法就是在暴力递归的基础上做一个填表过程。二维表的长度根据可变参数index和bag的长度决定,分析得出index的范围是0~N,bag的范围是负数到bag,所以二维表int[N+1][bag+1],

从暴力递归看出表中数据总是依赖上一行数据,所以便宜顺序从下到上,左右不影响。最后return的结果也是从暴力递归中得出dp[0][bag]。

    //动态规划解法
    public static int dp(int w[], int v[], int bag) {
        if (w == null || v == null || w.length != v.length) {
            return 0;
        }
        int N = w.length;
        int dp[][] = new int[N + 1][bag + 1];

        for (int index = N - 1; index >= 0; index--) {
            for (int rest = 0; rest <= bag; rest++) {
                int p1 = dp[index + 1][rest];
                int p2 = 0;
                int next = rest - w[index] < 0 ? -1 : dp[index + 1][rest - w[index]];
                if (next != -1) {
                    p2 = v[index] + next;
                }
                dp[index][rest] = Math.max(p1, p2);

            }
        }
        return dp[0][bag];
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值