算法篇——动态规划算法例题(路径问题)

193 阅读3分钟

内容二

Michael 喜欢滑雪。为了获得速度,滑的区域必须向下倾斜,而且当你滑到坡底, 不得不再次走上坡或者等付升降机来载你。

区域由一个二维数组给出。数组的每个数字代表点的高度。

当且仅当高度减小,一个人可以从某个点滑向上下 左右相邻四个点之一。

求:一个区域中最长的滑坡

请设计算法求解以上问题并做分析

转移方程:

dp[ i ][ j ] =max(dp[ i-1 ][ j ],dp[ i+1 ][ j ],dp[ i ][ j-1 ],dp[ i ][j+1])+1;

拓展:记忆化搜索

参考:oi-wiki.org/dp/memo/#_1

分析:比下面那道题复杂一点,复杂的点在于下面那道题是从某一固定点到另外一个固定点,这道题是计算任意一点到某一未知点的最大距离。所以相当于是遍历了滑雪场所有的点(每一个点都有可能是起点),然后对于每一个点都深搜用动态规划的办法求出最大值。

import java.util.Scanner;

/**
 * @author SJ
 * @date 2020/10/27
 */
public class GoSkating {

    public static int[][] ground;//滑雪场
    public static int X;//行
    public static int Y;//列

    public GoSkating(int x,int y) {
        X=x;
        Y=y;
        ground=new int[X][Y];
        dp=new int[X][Y];
    }
    public static void inputGround(){
        System.out.println("请输入二维数组");
        Scanner scanner = new Scanner(System.in);
        for (int i = 0; i < X; i++) {
            for (int i1 = 0; i1 < Y; i1++) {
                ground[i][i1]=scanner.nextInt();
            }
        }

    }

    public static int[][] dp;//最优解
    //测试输出
    public static void printDp(){
        for (int i = 0; i < dp.length; i++) {
            for (int i1 = 0; i1 < dp[i].length; i1++) {
                System.out.print(dp[i][i1]+" ");
            }
            System.out.println();
        }
    }
   //判断前后左右是不是比他大
    public static boolean isGradient(int x,int y,String s){
        switch (s){
            //左边是否比它高
            case "left":
                if (x-1>=0&&ground[x-1][y]>ground[x][y])
                    return true;
                break;
                //右边是否比它高
            case "right":
                if (x+1<Y&&ground[x+1][y]>ground[x][y])
                    return true;
                break;
                //上边是否比它低
            case "up":
                if (y-1>=0&&ground[x][y-1]>ground[x][y])
                    return true;
                break;
                //下边是否比它低
            case "down":
                if (y+1<X&&ground[x][y+1]>ground[x][y])
                    return true;
                break;



        }
        return false;
    }
    public static int dfs(int x,int y){
        int res=1;
        if (dp[x][y]!=0)
            return dp[x][y];
        if (isGradient(x,y,"left")){
            res=Math.max(res,dfs(x-1,y)+1);
        }
        if (isGradient(x,y,"right"))
            res=Math.max(res,dfs(x+1,y)+1);
        if (isGradient(x,y,"up"))
            res=Math.max(res,dfs(x,y-1)+1);
        if (isGradient(x,y,"down"))
            res=Math.max(res,dfs(x,y+1)+1);

        dp[x][y]=res;
        return dp[x][y];
    }

    public static void main(String[] args) {
        int x=5;
        int y=5;
        new GoSkating(x,y);
        inputGround();
        int res=-1;
        for (int i = 0; i < X; i++) {
            for (int j = 0; j < Y; j++) {
                res=Math.max(res,dfs(i,j));
            }

        }
        System.out.println("最大长度为:"+res);
       // printDp();

    }

}

测试:

"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe"
请输入二维数组
1 2 3 4 5
16 17 18 19 6
15 24 25 20 7
14 23 22 21 8
13 12 11 10 9
最大长度为:25

Process finished with exit code 0

对每个点进行深搜,遇到四面都是较大值时或者遇到dp中已经有过记录的点开始回朔。直至搜索完最后一个点。因为每个点只计算了一次,自然,这里的时间复杂度仅有O(X*Y)。

内容三

给定一个𝑛 ∗ 𝑚的矩阵,每个单元中都有 一个非负整数,只能向右或向下移动

求从左上角到右下角的所有路径中的最大 值(每条路径的值为对路径中所进过的格子中的数求和)

请设计算法求解以上问题并做分析

动态转移方程:

dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + nums[i][j];

本题有点简单的,没啥好讲思路的。

import java.util.Scanner;

/**
 * @author SJ
 * @date 2020/10/21
 */
public class PickUpApples {

    public static int[][] nums;
    public static int X;//行
    public static int Y;//列

    public static void inputNums() {
        System.out.println("请输入二维数组");
        Scanner scanner = new Scanner(System.in);
        for (int i = 0; i < X; i++) {
            for (int i1 = 0; i1 < Y; i1++) {
                nums[i][i1] = scanner.nextInt();
            }
        }
    }

    public PickUpApples(int x,int y) {
        X=x;
        Y=y;
        nums=new int[X][Y];
    }

    public static int pickMax() {

        int[][] dp = new int[X][Y];
        dp[0][0] = nums[0][0];
        int max=dp[0][0];
        //初始化第一排和第一列
        for (int i = 1; i < X; i++) {
            dp[0][i] = dp[0][i - 1] + nums[0][i];
            max=Math.max(max,dp[0][i]);

        }
        for (int i = 1; i < Y; i++) {
            dp[i][0] = dp[i - 1][0] + nums[i][0];
            max=Math.max(max,dp[i][0]);

        }
        //转移方程
        for (int i = 1; i < X; i++) {
            for (int j = 1; j < Y; j++) {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]) + nums[i][j];
                max=Math.max(max,dp[i][j]);
            }

        }



//        for (int[] ints : dp) {
//            System.out.println(Arrays.toString(ints));
//       }
        return max;
    }

    public static void main(String[] args) {
        int x=3;
        int y=3;
        new PickUpApples(3,3);
        inputNums();
        int i = pickMax();
        System.out.println(i);

    }


}

结果:

"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe"...
请输入二维数组
1 2 3
4 5 6
7 8 9
左上到右下的最大值:29

Process finished with exit code 0

时间复杂度O(XY)O(X*Y)

内容四

给定一个𝑛 ∗ 𝑚的矩阵,每个单元都有一 个值,现在从左上走到右下再回到左上, 中间不走重复的点,求所走单元的和的最 大值。

请设计算法求解以上问题并做分析。

分析:看成两个点(i,j)和 (k,d)同时从左上走到右下,保证中途不相交即可

这次采用备忘录方法(dp也能做,思想差不多)。

从右下开始一层层向左上递归。

而这里的dp是同时对两条不相交的路进行。于是memo数组就用四维的,memo[i][j][k][d]memo[i][j][k][d]表示同时走到[i][j][i][j][k][d][k][d]时的最大值,而走到每个点有左上两条路,两个点,则有四种组合情况

状态转移方程

memo[i][j][k][d]=max(max(memo[i-1][j][k-1][d],memo[i-1][j][k][d-1]),max(memo[i][j-1][k-1][d],memo[i][j-1][k][d-1]))+nums[i][j]+nums[k][d];
import java.util.Scanner;

/**
 * @author SJ
 * @date 2020/10/27
 */
public class FindMaxRoute {
    public static int[][] nums;
    public static int X;//行
    public static int Y;//列
    public static int[][][][] memo;

    public static void inputNums() {
        System.out.println("请输入二维数组");
        Scanner scanner = new Scanner(System.in);
        for (int i = 0; i < X; i++) {
            for (int i1 = 0; i1 < Y; i1++) {
                nums[i][i1] = scanner.nextInt();
            }
        }
    }

    public FindMaxRoute(int x, int y) {
        X = x;
        Y = y;
        nums = new int[X][Y];
        memo = new int[X][Y][X][Y];
    }

    //用备忘录解法
    public static int dfs(int i, int j, int k, int d) {
        //两个点(i,j)和(k,d)同时从右下下往右上角走
        if (memo[i][j][k][d] != 0)
            return memo[i][j][k][d];
        //递归触底
        if (i == 0 && j == 0 && k == 0 && d == 0)
            memo[i][j][k][d] = nums[0][0] * 2;
        else if (i == k && j == d &&  !(i == X - 1 && j==Y-1))//两点相交
        {
            return 0;
        } else //两个点四种可能
        {
            int temp1 = -1;
            //都向左
            if (i - 1 >= 0 && k - 1 >= 0)
                temp1 = Math.max(dfs(i - 1, j, k - 1, d) + nums[i - 1][j] + nums[k - 1][d], temp1);
            //左边的向左,右边上向上
            if (i - 1 >= 0 && d - 1 >= 0)
                temp1 = Math.max(dfs(i - 1, j, k, d - 1) + nums[i - 1][j] + nums[k][d - 1], temp1);
            //左边的向上,右边的向左
            if (j - 1 >= 0 && k - 1 >= 0)
                temp1 = Math.max(dfs(i, j - 1, k - 1, d) + nums[i][j - 1] + nums[k - 1][d], temp1);
            //都向上
            if (j - 1 >= 0 && d - 1 >= 0)
                temp1 = Math.max(dfs(i, j - 1, k, d - 1) + nums[i][j - 1] + nums[k][d - 1], temp1);
            memo[i][j][k][d] = temp1;

        }
        return memo[i][j][k][d];

    }

    //测试输出
    public static void printMemo() {
        for (int i = 0; i < X; i++) {
            for (int j = 0; j < Y; j++) {
                for (int k = 0; k < X; k++) {
                    for (int l = 0; l < Y; l++) {
                        System.out.print(memo[i][j][k][l]);
                    }
                    System.out.println();
                }
                System.out.println();
            }
            System.out.println();
        }
        System.out.println();
    }

    public static void main(String[] args) {
        int x = 3;
        int y = 3;
        new FindMaxRoute(3, 3);
        inputNums();
        int dfs = dfs(2, 2, 2, 2);
        System.out.println("最大值为:"+dfs);
        //printMemo();

    }

}

测试结果:

"C:\Program Files\Java\jdk1.8.0_131\bin\java.exe" ...
请输入二维数组
0 2 9
4 8 6
2 7 0
最大值为:36

Process finished with exit code 0

因为是四维数组,不管是动态规划还是备忘录,时间复杂度是O(m2n2)O(m^2n^2),备忘录用的还是递归,需要一层层回溯所以花的时间会多一些。