算法27:从暴力递归到动态规划(2)

这篇具有很好参考价值的文章主要介绍了算法27:从暴力递归到动态规划(2)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

上一题比较简单,下面来一道比较难的题目。


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

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

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

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

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

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

给定四个参数 NMKP返回方法数。

解题思路:

1. 如果机器人在1的位置,只能从左到右

2.如果机器人在N的位置,只能从右到左

3.如果机器人在中间,既可以往右、也可以往左

现在的题目是要求返回多少种走法,也就是只要能到达目的地,并且步数足够,我们随意走都可以。

遇到这种题目,绘图是最好的方式:

假设一共有6个位置,从2位置出发,走5步,到达位置5,一共有多少种走法

算法27:从暴力递归到动态规划(2)

递归方式 

package code03.动态规划_07;

/**
 * 假设有排成一行的N个位置,记为1~N,N 一定大于或等于 2
 * 开始时机器人在其中的M位置上(M 一定是 1~N 中的一个)
 * 如果机器人来到1位置,那么下一步只能往右来到2位置;
 * 如果机器人来到N位置,那么下一步只能往左来到 N-1 位置;
 * 如果机器人来到中间位置,那么下一步可以往左走或者往右走;
 * 规定机器人必须走 K 步,最终能来到P位置(P也是1~N中的一个)的方法有多少种
 * 给定四个参数 N、M、K、P,返回方法数。
 */
public class Robot_02 {
    /**
     *
     * @param mStart   M 开始位置
     * @param kSteps   K 步数
     * @param nAll     N 长度
     * @param pAim     P 目的地位置
     * @return
     */
    public static int way1(int mStart, int kSteps, int nAll, int pAim)
    {
        return process(mStart, kSteps, nAll, pAim);
    }

    public static int process ( int mStart, int kSteps, int nAll, int pAim)
    {
        //步数为0,没法走
        if (kSteps == 0) {
           return mStart == pAim ? 1 : 0;
        }
        //只能往右走
        if (mStart == 1) {
            return process(mStart + 1, kSteps-1, nAll, pAim);
        }
        //只能往左走
        if (mStart == nAll) {
            return process(nAll-1, kSteps-1, nAll, pAim);
        }
        //中间位置,即可往左,也可往右,需要统计出往左往右的和
        return process(mStart +1, kSteps-1, nAll, pAim)
                +  process( mStart -1, kSteps-1, nAll, pAim);
    }

    public static void main(String[] args) {
        System.out.println(way1(2,5,6,5));
    }
}

同样的问题,我们分析可以得到:

在2位置,剩余5步,有5中走法

在3位置,剩余4步,有4种走法

在4位置,剩余3步,有3种走法

在5位置,剩余2步,有2种走法;

在6位置,剩余1步,有1种走法

中间存在来来回回重复的走法,这说明我们存在重复的推导结果,自然就过渡到递归+动态规划的方式进行优化了。

递归 + 动态规划

package code03.动态规划_07;

/**
 * 假设有排成一行的N个位置,记为1~N,N 一定大于或等于 2
 * 开始时机器人在其中的M位置上(M 一定是 1~N 中的一个)
 * 如果机器人来到1位置,那么下一步只能往右来到2位置;
 * 如果机器人来到N位置,那么下一步只能往左来到 N-1 位置;
 * 如果机器人来到中间位置,那么下一步可以往左走或者往右走;
 * 规定机器人必须走 K 步,最终能来到P位置(P也是1~N中的一个)的方法有多少种
 * 给定四个参数 N、M、K、P,返回方法数。
 */
public class Robot_02_opt1 {
    /**
     * @param mStart   M 开始位置  [0-nAll]
     * @param kSteps   K 步数
     * @param nAll     N 长度
     * @param pAim     P 目的地位置
     * @return
     */
    public static int way1(int mStart, int kSteps, int nAll, int pAim)
    {
        /**
         * 位置是1-N, 每一个位置都存在不同的走法,以每个位置为行,那么行的范围就是1-N
         * 由于java数组下标从0开始,想要获取1-N范围,长度需要加1
         *
         * 我们将剩余步数为列,这样每个位置剩余步数就可以很直观的观察到
         */
        int[][] dp = new int[nAll + 1][kSteps+1]; //剩余步数为横坐标,当前位置为纵坐标
        for (int i = 0; i < dp.length; i++) {
            for(int j = 0; j <dp[i].length; j++) {
                dp[i][j] = -1;
            }
        }
        int result = process(mStart, kSteps, nAll, pAim, dp);
        return process(mStart, kSteps, nAll, pAim, dp);
    }

    public static int process ( int mStart, int kSteps, int nAll, int pAim, int[][] dp)
    {
        if (dp[mStart][kSteps] != -1) {
            return dp[mStart][kSteps];
        }

        int result;
        if (kSteps == 0) {
            result = mStart == pAim ? 1 : 0;
        }
        //只能往右走
        else if (mStart == 1) {
            result =  process(mStart + 1, kSteps-1, nAll, pAim, dp);
        }
        //只能往左走
        else if (mStart == nAll) {
            result = process(nAll-1, kSteps-1, nAll, pAim, dp);
        }
        //中间位置,即可往左,也可往右,需要统计出往左往右的和
        else{
            result = process(mStart +1, kSteps-1, nAll, pAim, dp)
                    +  process( mStart -1, kSteps-1, nAll, pAim, dp);
        }
        dp[mStart][kSteps] = result;

        return result;
    }

    public static void main(String[] args) {
        System.out.println(way1(2,5,6,5));
    }
}

纯动态规划方式

1. 根据递归的方式,我们知道,如果剩余步数为0,那么开始位置如果正好在目标位置处,就算是1种走法。并且可以推导二维数组的第一列,第五行为1,其他都为0

if (kSteps == 0) {
   return mStart == pAim ? 1 : 0;
}
x x x x x x
0
0
0
0
1
0

2. 根据递归方式,如果在最左端,只能往右走; 如果在最右端,只能往左走。目前第一列数据已经推导出来了。因此需要根据第一列进行推导。

//只能往右走
if (mStart == 1) {
    return process(mStart + 1, kSteps-1, nAll, pAim);
}
//只能往左走
if (mStart == nAll) {
    return process(nAll-1, kSteps-1, nAll, pAim);
}

根据递归代码,我们知道,无论是往左走,还是往右走。

第二行第二列,也就是dp[1][1],  都是下一行的前一列数据. 而最后一行最后一列,都是上一行前一列的数据。推导结果》

x x x x x x
0 0
0
0
0
1
0 1

3. 根据递归方法中,如果在中间位置。需要得到上一行和下一行的前一列累加值

//中间位置,即可往左,也可往右,需要统计出往左往右的和
return process(mStart +1, kSteps-1, nAll, pAim)
        +  process( mStart -1, kSteps-1, nAll, pAim);

推导出结果

x x x x x x
0 0
0 0
0 0
0 1
1 0
0 1

依次类推,得到的最终值为:

剩余0 剩余1 剩余2 剩余3 剩余4 剩余5
目标位置0 x x x x x x
目标位置1 0 0 0 0 1 0
目标位置2 0 0 0 1 0 5
目标位置3 0 0 1 0 4 0
目标位置4 0 1 0 3 0 9
目标位置5 1 0 2 0 5 0
目标位置6 0 1 0 2 0 9

根据题目我们知道,位置是从1到N的,也就是说位置不存在0的情况。而我们这张表位置作为行,剩余步数作为列的。因此位置为0直接忽略。

假设一共有6个位置,从2位置出发,走5步,到达位置5,一共有多少种走法

dp[2][5]可得到结果为5,即5中种走法。文章来源地址https://www.toymoban.com/news/detail-494035.html

package code03.动态规划_07;

/**
 * 假设有排成一行的N个位置,记为1~N,N 一定大于或等于 2
 * 开始时机器人在其中的M位置上(M 一定是 1~N 中的一个)
 * 如果机器人来到1位置,那么下一步只能往右来到2位置;
 * 如果机器人来到N位置,那么下一步只能往左来到 N-1 位置;
 * 如果机器人来到中间位置,那么下一步可以往左走或者往右走;
 * 规定机器人必须走 K 步,最终能来到P位置(P也是1~N中的一个)的方法有多少种
 * 给定四个参数 N、M、K、P,返回方法数。
 */
public class Robot_02_opt2 {
    /**
     *
     * @param mStart   M 开始位置
     * @param kSteps   K 步数
     * @param nAll     N 长度
     * @param pAim     P 目的地位置
     * @return
     */
    public static int way(int nAll, int mStart, int pAim, int kSteps)
    {
        if (nAll < 2 || mStart < 1 || mStart > nAll || pAim < 1 || pAim > nAll || kSteps < 1) {
            return -1;
        }

        /**
         * 位置是1-N, 每一个位置都存在不同的走法,以每个位置为行,那么行的范围就是1-N
         * 由于java数组下标从0开始,想要获取1-N范围,长度需要加 1
         *
         * 我们将剩余步数为列,这样每个位置剩余步数就可以很直观的观察到.
         * 同理,列的长度也需要加 1。
         *
         * 其实,此处列无所谓的,哪怕就是100也行。只不过如果是100,那将会推导出
         * 走100的情况,无谓浪费资源。 最好的方式就是根据实际的业务,你要我推导
         * 几步,我就推导几步,节约资源
         */
        int[][] dp = new int[nAll+1][kSteps+1];
        //剩余步数为0,推导出对应哪一行为1
        dp[pAim][0] = 1;
        //根据剩余步数,推导出每一个位置能够走到目标位置的结果
        for (int col = 1; col <= kSteps; col++)
        {
            //第2行的每一列值, 对应的都是第3行前一列值
            dp[1][col] = dp[2][col-1];

            //从第二行开始,倒数第二行执行完后结束
            for (int row = 2; row < nAll; row++)
            {
                //中间行,等于上一行和下一行的前一列的和
                dp[row][col] = dp[row-1][col-1] + dp[row+1][col-1];
            }
            //最后一行,每一列值都对应上一行的前一列的值
            dp[nAll][col] = dp[nAll-1][col-1];
        }

        //根据当前哪个位置,剩余步数。获取到目标位置的不同走法
        return dp[mStart][kSteps];
    }



    public static void main(String[] args) {
        System.out.println(way(6,2,5,5));
    }
}

到了这里,关于算法27:从暴力递归到动态规划(2)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 暴力递归到动态规划(三)

    ⭐️ 前言 ⭐️ 本篇文章是从暴力递归到动态规划的第三章。 🍉 欢迎点赞 👍 收藏 ⭐ 留言评论 📝 私信必回哟 😁 🍉 博主将持续更新学习记录收获,友友们有任何问题可以在评论区留言 🍉 博客中涉及源码及博主日常练习代码均已上传GitHub 题目: 给定一个二维数组mat

    2024年02月09日
    浏览(38)
  • 暴力递归到动态规划(四)

    ⭐️ 前言 ⭐️ 本篇文章是从暴力递归到动态规划篇目的最后一篇文章,包含了几道题目还有最终的大总结,相信这篇文章能让各位读者对动态规划有更深一步的了解。 🍉 欢迎点赞 👍 收藏 ⭐ 留言评论 📝 私信必回哟 😁 🍉 博主将持续更新学习记录收获,友友们有任何问

    2024年02月08日
    浏览(42)
  • 左程云 Java 笔记--暴力递归--动态规划

    暴力递归就是尝试 1,把问题转化为规模缩小了的同类问题的子问题 2,有明确的不需要继续进行递归的条件(base case) 3,有当得到了子问题的结果之后的决策过程, 4,不记录每一个子问题的解 打印n层汉诺塔从最左边移动到最右边的全部过程 打印一个字符串的全部子序列,包

    2023年04月08日
    浏览(48)
  • 一篇文章带你搞懂动态规划(由暴力递归到动态规划)

    ”动态规划“ 的过程相当于 记忆化搜索 , 即在普通 递归 的过程中用二维数组进行记忆化存储一些状态结果, 从而避免重复的计算(剪枝)。 举一个简单的例子:在 递归法 求解 斐波那契 数列的过程中, 就进行了多次的 重复计算 , 而动态规划相当于是对已经计算的状态

    2024年02月20日
    浏览(54)
  • 题解 | #上台阶#C++暴力动态规划解法,非递归

    25届想找实习求看看简历 英伟达笔试 Nvidia24秋招 英伟达嵌入式软件工程师笔试 9-26 2022-08-17-nvidia实习 我发现算法岗也不很难进啊(深度学习) 我发现算法岗也不很难进啊(深度学习) 顺丰科技 1.30校招实习招聘信息汇总 2024春招汇总 『 哨哥的校园招聘周报 』02/05 - 02/18 深圳银河创

    2024年02月21日
    浏览(37)
  • 爬楼梯问题-从暴力递归到动态规划(java)

    一个总共N 阶的楼梯(N 0) 每次只能上一阶或者两阶。问总共有多少种爬楼方式。 示例1: N = 1, 一步上去了,返回1. 示例2: N = 2时。 可以第一次上一阶,再上一阶,这是一种方式, 也可以一次直接上两阶,这也是一种方式, 返回 2; 示例3: N = 3: 可以选择, 1 1 1, 1

    2024年02月10日
    浏览(36)
  • 【LeetCode:72. 编辑距离 | 暴力递归=>记忆化搜索=>动态规划 】

    🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,CSDN-Java领域新星创作者🏆,保研|国家奖学金|高中学习JAVA|大学完善JAVA开发技术栈|面试刷题|面经八股文

    2024年02月06日
    浏览(54)
  • 零钱兑换,凑零钱问题,从暴力递归到动态规划(java)

    322 零钱兑换 - 可以打开链接测试 给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。 你可以认为每种硬币的数量是无限的。 示例 1:

    2024年02月07日
    浏览(38)
  • 【LeetCode:64. 最小路径和 | 暴力递归=>记忆化搜索=>动态规划 】

    🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,CSDN-Java领域新星创作者🏆,保研|国家奖学金|高中学习JAVA|大学完善JAVA开发技术栈|面试刷题|面经八股文

    2024年02月05日
    浏览(44)
  • 【LeetCode: 44. 通配符匹配 | 暴力递归=>记忆化搜索=>动态规划 】

    🚀 算法题 🚀 🌲 算法刷题专栏 | 面试必备算法 | 面试高频算法 🍀 🌲 越难的东西,越要努力坚持,因为它具有很高的价值,算法就是这样✨ 🌲 作者简介:硕风和炜,CSDN-Java领域优质创作者🏆,保研|国家奖学金|高中学习JAVA|大学完善JAVA开发技术栈|面试刷题|面经八股文

    2024年02月06日
    浏览(75)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包