算法基础之暴力递归到动态规划

一斐波那契数列:

public class Fibonacci {
    public static int f(int n) {
        if (n == 1) {
            return 1;
        }
        if (n == 2) {
            return 1;
        }
        return f(n - 1) + f(n - 2);
    }
}

中间存在大量的重复过程:可以缓存起来

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

题目二

假设有排成一行的N个位置,记为1~N,N一定大于或等于2

开始时机器人在其中的M位置上(M一定是1~N中的一个)

如果机器人来到1位置,那么下一步只能往右来到2位置;

如果机器人来到N位置,那么下一步只能往左来到N-1位置﹔

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

规定机器人必须走K步,最终能来到P位置(P也是1~N中的一个)的方法有多少种

给定四个参数N、M、K、P,返回方法数。

递归方法:

package com.zh.class18;

public class RobotWalk {
    public static void main(String[] args) {
        System.out.println(ways(4, 2, 4, 4));
    }
    public static int ways(int N, int M, int K, int P) {
        if (N < 2 || M < 1 || M > N || P < 1 || P > N || K < 1) {
            return -1;
        }
        return process(M, K, P, N);
    }

    /**
     *
     * @param cur 当前的位置
     * @param rest 剩余的步数
     * @param aim 目标位置
     * @param N 所有的位置数目
     * @return 返回满足的数目
     */
    private static int process(int cur, int rest, int aim, int N) {
        if (rest == 0) {
            return cur == aim ? 1 : 0;
        }
        if (cur == 1) {
            return process(2, rest - 1, aim, N);
        }
        if (cur == N) {
            return process(N - 1, rest - 1, aim, N);
        }
        return process(cur - 1, rest - 1, aim, N) + process(cur + 1, rest - 1, aim, N);
    }
}

从递归中可以看出cur和rest是决定递归的key值,并且有重复过程,那么可以使用缓存的方法(即记忆化搜索):

cur的范围是1 ~ N,rest的范围是0 ~ K

package com.zh.class18;

public class RobotWalk {
    public static void main(String[] args) {
        System.out.println(ways(4, 2, 4, 4));
        System.out.println(ways1(4, 2, 4, 4));
    }
 

    public static int ways1(int N, int M, int K, int P) {
        if (N < 2 || M < 1 || M > N || P < 1 || P > N || K < 1) {
            return -1;
        }
        int[][] dp = new int[N + 1][K + 1];
        for (int i = 0; i <= N; i++) {
            for (int j = 0; j <= K; j++) {
                dp[i][j] = -1;
            }
        }
        return process1(M, K, P, N, dp);
    }

    /**
     *
     * @param cur 当前的位置 范围是1 ~ N
     * @param rest 剩余的步数 范围是0 ~ K
     * @param aim 目标位置
     * @param N 所有的位置数目
     * @return 返回满足的数目
     */
    private static int process1(int cur, int rest, int aim, int N, int[][] dp) {
        if (dp[cur][rest] != -1) {
            return dp[cur][rest];
        }
        int ans = 0;
        if (rest == 0) {
            ans =  cur == aim ? 1 : 0;
        } else if (cur == 1) {
            ans =  process1(2, rest - 1, aim, N, dp);
        } else if (cur == N) {
            ans = process1(N - 1, rest - 1, aim, N, dp);
        } else {
            ans = process1(cur - 1, rest - 1, aim, N, dp) + process1(cur + 1, rest - 1, aim, N, dp);
        }
        dp[cur][rest] = ans;
        return ans;
    }
}

再次优化为动态规划:

    public static int ways2(int N, int M, int K, int P) {
        if (N < 2 || M < 1 || M > N || P < 1 || P > N || K < 1) {
            return -1;
        }
        int[][] dp = new int[N + 1][K + 1];
        dp[P][0] = 1;
        for (int rest = 1; rest <= K; rest++) {
            dp[1][rest] = dp[2][rest - 1];

            for (int cur = 2; cur < N; cur++) {
                dp[cur][rest] = dp[cur - 1][rest - 1] + dp[cur + 1][rest - 1];
            }

            dp[N][rest] = dp[N - 1][rest - 1];
        }
        return dp[M][K];
    }

题目三:

给定一个整型数组arr,代表数值不同的纸牌排成一条线

玩家A和玩家B依次拿走每张纸牌

规定玩家A先拿,玩家B后拿,但是每个玩家每次只能拿走最左或最右的纸牌

玩家A和玩家B都绝顶聪明

请返回最后获胜者的分数。

暴力递归方法:

package com.zy.class002;

public class CardsInLine {
    public static void main(String[] args) {
        int[] arr = {5, 7, 4, 5, 8, 1, 6, 0, 3, 4, 6, 1, 7};
        System.out.println(win1(arr));
    }
    // 根据规则,返回获胜者的分数
    public static int win1(int[] arr) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        int first = first(arr, 0, arr.length - 1);
        int second = after(arr, 0, arr.length - 1);
        return Math.max(first, second);
    }

    // 先手获得的分数
    public static int first(int[] arr, int left, int right) {
        if (left == right) {
            return arr[left];
        }

        int p1 = arr[left] + after(arr, left + 1, right); // 对手拿走了left位置
        int p2 = arr[right] + after(arr, left, right - 1); // 对手拿走了right位置
        return Math.max(p1, p2);

    }

    // 后手获得最好的分数
    public static int after(int[] arr, int left, int right) {
        // 因为是后手,只能是0
        if (left == right) {
            return 0;
        }
        int p1 = first(arr, left + 1, right);
        int p2 = first(arr, left, right - 1);
        // 因为是后手,都是千年的护理,所以只能给你最小的
        return Math.min(p1, p2);
    }
}

 但是暴力递归是存在重复值的:因此可以使用缓存的方法来优化:

package com.zy.class002;

public class CardsInLine2 {
    public static void main(String[] args) {
        int[] arr = {5, 7, 4, 5, 8, 1, 6, 0, 3, 4, 6, 1, 7};
        System.out.println(win1(arr));
    }
    // 根据规则,返回获胜者的分数
    public static int win1(int[] arr) {
        if (arr == null || arr.length == 0) {
            return 0;
        }
        int len = arr.length;
        int[][] fdp = new int[len][len];
        int[][] adp = new int[len][len];
        for (int i = 0; i < len; i++) {
            for (int j = 0; j < len; j++) {
                fdp[i][j] = -1;
                adp[i][j] = -1;
            }
        }
        int first = first(arr, 0, arr.length - 1, fdp, adp);
        int second = after(arr, 0, arr.length - 1, fdp, adp);
        return Math.max(first, second);
    }

    // 先手获得的分数
    public static int first(int[] arr, int left, int right, int[][] fdp, int[][] adp) {
        if (fdp[left][right] != -1) {
            return fdp[left][right];
        }
        int ans = 0;
        if (left == right) {
            ans =  arr[left];
        } else {
            int p1 = arr[left] + after(arr, left + 1, right, fdp, adp); // 对手拿走了left位置
            int p2 = arr[right] + after(arr, left, right - 1, fdp, adp); // 对手拿走了right位置
            ans = Math.max(p1, p2);
        }
        fdp[left][right] = ans;
        return ans;
    }

    // 后手获得最好的分数
    public static int after(int[] arr, int left, int right, int[][] fdp, int[][] adp) {
        int ans = 0;
        if (left != right) {
            int p1 = first(arr, left + 1, right, fdp, adp);
            int p2 = first(arr, left, right - 1, fdp, adp);
            ans = Math.min(p1, p2);
        }
        // 因为是后手,都是千年的护理,所以只能给你最小的
        return ans;
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值