1. 简介

动态规划是为了优化暴力尝试的。

2. 求n!

2.1 一般思路

public static long getFactorial2(int n) {long result = 1L;for (int i = 1; i <= n; i++) {result *= i;}return result;
}

2.2 递归

  • 要求n!,求出(n-1)!,再乘以n即可;
  • 求(n-1)!,先求出(n-2)!,再乘以(n-1)即可;
  • ...
  • 需要有个Base case,即n=1时,不再递归,直接返回1。

即:n!的结果依赖于n-1;n-1依赖于n-2;...;2依赖于1。

计算的时候,将顺序逆过来,由1得到2,再得到3,...,再得到n-1,得到n。

一般方法的思路:把不依赖于别的的状态给出,然后计算依赖于它的的状态,在往后计算

public static long getFactorial1(int n) {if (n == 1) {return 1L;}return (long) n * getFactorial1(n - 1);
}

3.汉诺塔问题

打印n层汉诺塔从最左边移动到最右边的全部过程

3.1 简介

如图:

将左杆上的盘全部移到右杆上,中杆可作为辅助,要求大盘不能压小盘。

3.1.1 具体步骤

3.2 递归思路分析

有from,to,help三个杆,1~N个盘在from杆上,需把它们移到to杆上。可以利用help杆。

  1. 需把1~n-1从from挪到help上;
  2. 把单独的n挪到to上;
  3. 把1~n-1从help挪到to上;
package Recursive;public class Hanoi {public static void main(String[] args) {hanoi(3,"左","右","中");}public static void hanoi(int N,String from,String to,String help) {if(N==1)System.out.println("Move "+N+" from "+from +" to "+to);else {hanoi(N-1,from,help,to);System.out.println("Move "+N+" from "+from +" to "+to);hanoi(N-1,help,to,from);}}
}

运行结果:

3.3 将上述递归过程拆为6个子过程

  1. L->R
  2. L->M
  3. R->M
  4. R->L
  5. M->L
  6. M->R

L->R(M作为辅助)具体包括:

  1. L->M:1~N-1
  2. L->R:N
  3. M->R:1~N-1

L->M(R作为辅助)具体包括:

  1. L->R:1~N-1
  2. L->M:N
  3. R->M:1~N-1

后面四个步骤类似。

3.4 代码实现

package Recursive;public class Hanoi {public static void main(String[] args) {moveLeftToRight(3);}public static void moveLeftToRight(int N) {if(N==1)System.out.println("Move "+N+" from 左  to 右");else{moveLeftToMiddle(N-1);System.out.println("Move "+N+" from 左  to 右");moveMiddleToRight(N-1);}}public static void moveLeftToMiddle(int N) {if(N==1)System.out.println("Move "+N+" from 左  to 中");else{moveLeftToRight(N-1);System.out.println("Move "+N+" from 左  to 中");moveRightToMiddle(N-1);}}public static void moveMiddleToRight(int N) {if(N==1)System.out.println("Move "+N+" from 中  to 右");else{moveMiddleToLeft(N-1);System.out.println("Move "+N+" from 中  to 右");moveLeftToRight(N-1);}}public static void moveRightToMiddle(int N) {if(N==1)System.out.println("Move "+N+" from 右  to 中");else{moveRightToLeft(N-1);System.out.println("Move "+N+" from 右  to 中");moveLeftToMiddle(N-1);}}public static void moveRightToLeft(int N) {if(N==1)System.out.println("Move "+N+" from 右  to 左");else{moveRightToMiddle(N-1);System.out.println("Move "+N+" from 右  to 左");moveMiddleToLeft(N-1);}}public static void moveMiddleToLeft(int N) {if(N==1)System.out.println("Move "+N+" from 中  to 左");else{moveMiddleToRight(N-1);System.out.println("Move "+N+" from 中  to 左");moveRightToLeft(N-1);}}
}

4.打印字符串的子序列(不是子串),包括空序列

4.1 分析

每走一步,都有两个决策:

  • 加当前位置上的字符
  • 不加这个字符

4.2 代码实现

package Recursive;public class PrintAllSub {public static void main(String[] args) {String str="abc";printAllSub(str.toCharArray(),0,"");}public static void printAllSub(char[] str,int i,String res) {if(i==str.length) {System.out.println(res);return;}printAllSub(str,i+1,res);printAllSub(str,i+1,res+str[i]);}
}

运行结果:

5.打印一个字符串的全排列

5.1 分析

第一步:确认第一个位置的字符,即第一个位置的字符一次和后面位置的字符进行交换

第二步:确认第二个位置的字符,...

...

最后,知道表示位置的index=字符串长度时,打印。

5.2 代码实现

package Recursive;public class PrintAllPermutations {public static void main(String[] args) {String str="abc";printAllPermutations(str);}public static void printAllPermutations(String str) {char[] chs=str.toCharArray();process(chs,0);}public static void process(char[] chs,int i) {if(i==chs.length) {System.out.println(chs);return;}else {for(int j=i;j<chs.length;j++) {swap(chs,i,j);process(chs,i+1);swap(chs,i,j);//回归原始位置,继续下一轮交换}}}public static void swap(char[] chs, int i,int j) {char temp=chs[i];chs[i]=chs[j];chs[j]=temp;}
}

运行结果:

6.母牛生小牛

母牛每年生一只母牛,新出生的母牛成长三年后也能每年生一只母牛,假设牛不会死。

求N年后,母牛的数量。

6.1 分析

规律:

原因:第N年的牛个数=第N-1年牛的个数(牛不会死,去年的牛会保留到今年来)+向前数三年的牛的个数(新生的牛=可以生牛的母牛个数)

做法:写出前几项,得出规律,看规律是否合理。

时间复杂度:

有个更优的,时间复杂度为

  • 利用线性代数中矩阵乘法来解。
  • 斐波那契数列也存在的解

7.暴力递归试出动态规划——最小路径和

给一个二维数组,其中的每个数都是正数。要求从左上角走到右下角,每一步只能向右或者向下。沿途经过的数字要累加起来,返回最小的路径和。

7.1 递归版本

package Recursive;public class MinRoadSum {public static void main(String[] args) {int[][] arr= {{3,2,1,0},{7,5,0,1},{3,7,6,2}};System.out.println(minRoadSum(arr));}public static int minRoadSum(int[][] arr) {int res=process(arr,0,0);//当前数组,当前的行号,当前列号,当前路径和return res;}//从(i,j)出发,到达右下角的位子public static int process(int[][] arr,int i,int j) {int hlen=arr.length-1;int llen=arr[0].length-1;if(i==hlen&&j==llen) return arr[i][j];if(i==hlen)//只能往右走return arr[i][j]+process(arr,i,j+1);if(j==llen)//只能往下右走return arr[i][j]+process(arr,i+1,j);int down=process(arr,i+1,j);//下边位置到右下角的最短路径和int right=process(arr,i,j+1);return arr[i][j]+Math.min(down, right);}
}

这是暴力:

大量重复解产生,如上图中的f(1,1)。

整个过程中,重复状态很多——暴力递归不行的原因。

7.2 改进

在求解子过程时,将结果保存,下次用到的时候直接拿,可减少计算量。

——把1-1作为一个key,将f(1,1)作为value,存入哈希表中

7.3 递归可以改为动态规划的要求

——在展开递归的过程中,有重复的计算,而且这个重复的状态与到达它的路径无关。

比如点(1,1),它到右下角点的最短路径与(0,0)是通过哪条路径到达它的无关。

——这种问题叫做无后效性问题。——只要参数定了(如,1,1),返回值就确定了(f(1,1)是一定的)。

有后效性问题:汉诺塔问题、N皇后问题。——之前作出的选择会影响后面的决策。

上题中,i和j一旦确定,返回值就是确定的。那么可以将所有的返回值存在和路径数组大小一样的数组dp中。

  • dp数组中位置(i,j)表示:路径数组中的位置(i,j)到右下角点的最短路径和。

我们要的最终结果是(0,0)位置的值,标为※,

然后看可以确定的位置的值——base case,即右下角位置的值,就是matrix中右下角位置的值0;

if(i==hlen&&j==llen) return arr[i][j];

接着看设置不被依赖的值:最后一行和最后一列

如果在最后一行,则当前位置的值=当前matrix位置中对应的值+右边位置的最短路径和(即dp中此位置右边的值)

if(i==hlen)//只能往右走return arr[i][j]+process(arr,i,j+1);if(j==llen)//只能往下走return arr[i][j]+process(arr,i+1,j);

最后一列也一样:

接着分析一个普遍位置是怎么依赖的:

int down=process(arr,i+1,j);//下边位置到右下角的最短路径和
int right=process(arr,i,j+1);return arr[i][j]+Math.min(down,right);

在当前(i,j),它需要:

  • 右边的位置:(i,j+1)
  • 左边的位置:(i+1,j)
  • 选两者中较小的

由于最后一行和最后一列已确定,则右下角位置的左上方的值就可以确定。它左边的位置也可以求出来:

中间的位置从右到左,再从下到上,依次推倒顶部,即可得到答案。

以上就是暴力递归改为动态规划的统一套路。

  • 写出可变版本
  • 分析可变参数
    • 哪些可变参数可以代表返回值的状态
    • 参数时几维的,dp就是一张几维表
  • 在dp中标出最终需要的点
  • 回到base case中,将完全不依赖的位置的值设置好
  • 分析一个普遍位置需要哪些位置,逆着回去,就会说填表的顺序。

8.累加和是否可达到给定值

给定一个数组arr,其中的数全为正数。如果可以任意选择arr中的数字。能不能累加得到正数aim。返回true或false。

8.1 分析

和子序列一样

比如给定:

3,2,7,13

aim=9。是否能加到9?

f(0,0):目前在0位置,形成的和是0;

可按照子序列的方法分析,在每个位置选择要不要前一个位置的数。

到最后一层的时候,如果发现有一个累加和为9,返回true,然后一直往上,每一层只要有一个true,则最后结果肯定为true。

8.2 代码实现

package Recursive;public class IsHaveSum {public static void main(String[] args) {int[] arr= {1,4,8};int aim=12;System.out.println(isHaveSum(arr,0,0,aim));}public static boolean isHaveSum(int[] arr,int i,int sum,int aim) {if(i==arr.length)return sum==aim;return isHaveSum(arr,i+1,sum,aim)||isHaveSum(arr,i+1,sum+arr[i],aim);}
}

8.3 分析是否有后效性

一个序列为3,2,5,...:

  • 选3,2,没选5,则是f(3,5)
  • 没选3,2,选了5,还是f(3,5)

所以是无后效性的。

3和5一旦确定,则返回值肯定确定。

(arr,i,sum,aim):四个参数中,arr和aim是确定的,i和sum是变化的——二维表

  • LEN:数组中元素的个数,i的最大值;
  • SUM:数组中所有元素的和,sum的最大值。

f(i,sum)肯定可以装在上面两个参数形成的二维表中。

8.4 转换

8.4.1 找出最终需要的状态

8.4.2 找出基本状态

if(i==arr.length)//最后一行return sum==aim;

aim>SUM直接返回false;

aim<SUM,则在最后一列SUM对应的位置为T(True)

0~SUM之间是以1为单位递增的。

8.4.3 分析普遍状态

return isHaveSum(arr,i+1,sum,aim)||isHaveSum(arr,i+1,sum+arr[i],aim);

状态(i,sum),依赖的状态有:

  • (i+1,sum):下一行正下面的状态;
  • (i+1,sum+arr[i]):假设arr[i]=a,则指的是下一行正下方往右推a个单位的状态。

8.5 若给定数组中有负值

SUM的范围:负值之和~正值之和

注意下标的对应。

比如:3,-2,5

则SUM的范围:-2~8

下标的范围:0~10

算法练习day15——190403(简介、求n!、汉诺塔、打印字符串的子序列、打印字符串的全排列、母牛生小牛、最小路径和、累加和是否达到给定值)相关推荐

  1. 汉诺塔算法 c语言实验报告,C语言汉诺塔算法原理分析与实践

    汉诺塔游戏的规则:如下图所示,有三个柱子A,B,C,我们要做的是把A柱的所有圆盘,全部转移到C柱上,转移时遵循的规则如下: 1.每次只能移动一个圆盘 2.所有的大圆盘必须在小圆盘的下面 首先假设只有一 ...

  2. 超级青蛙、汉诺塔、汉诺塔Ⅱ、kimi的早餐店、字母全排列

    题目描述 一只超级青蛙一次可以跳上1级台阶,也可以跳上2级--它也能够跳上n级台阶.请问,该青蛙跳上一个n级的台阶总共有多少种跳法? 输入 输入一个正整数n表示台阶的数量. 输出 输出总的跳法数. 样 ...

  3. c语言叠罗汉问题解决思路,如何利用分治算法解决 ‘叠罗汉’ 问题,也叫作汉诺塔问题...

    问题: 将A塔的所有盘子移动到C塔,期间可借助B塔,但一直要保证大盘下,小盘上 使用java写了分治算法,化大为小,分而治之,加上递归,便可轻松解决这个问题! static int step = 0; ...

  4. 四柱加强版汉诺塔HanoiTower----是甜蜜还是烦恼

    我想很多人第一次学习递归的时候,老师或者书本上可能会举汉诺塔的例子. 但是今天,我们讨论的重点不是简单的汉诺塔算法,而是三柱汉诺塔的延伸.先来看看经典的三柱汉诺塔. 一.三柱汉诺塔(Hanoi_Thr ...

  5. 多柱汉诺塔最优算法设计探究

    多柱汉诺塔最优算法设计探究   引言 汉诺塔算法一直是算法设计科目的最具代表性的研究问题,本文关注于如何设计多柱汉诺塔最优算法的探究.最简单的汉诺塔是三个柱子(A.B.C),因此多柱汉诺塔的柱子个数M ...

  6. C语言编程求解圆盘的汉诺塔,课内资源 - 基于80x86汇编的汉诺塔

    一.软件背景介绍 我们今天要陈述的应用叫做汉诺塔,大家可能小时候都接触过类似于鲁班锁,九连环的益智玩具,我们要说的汉诺塔其实也可以说是益智玩具的一种. 下面我们具体介绍一下汉诺塔.汉诺塔有三根杆子A, ...

  7. python汉诺塔_汉诺塔递归算法/搬金盘的婆罗门 - Python实现

    汉诺塔递归算法/搬金盘的婆罗门 - Python实现 版权声明 本文节选自作者本人的图书<Python编程基础及应用>,高等教育出版社.本文可以在互联网上自由转载,但必须:注明出处(作者: ...

  8. 汉诺塔python创新设计_递归经典案例汉诺塔 python实现

    最近在廖雪峰大神的教程学习python 学到递归的时候有个汉诺塔的练习,汉诺塔应该是学习计算机递归算法的经典入门案例了,因此本人以为能够写篇博客来表达一下本身的看法.这markdown编辑器还不怎么会 ...

  9. 汉诺塔递归算法/搬金盘的婆罗门 - Python实现

    汉诺塔递归算法/搬金盘的婆罗门 - Python实现 本文引用自作者编写的下述图书; 本文允许以个人学习.教学等目的引用.讲授或转载,但需要注明原作者"海洋饼干叔 叔":本文不允许 ...

  10. 汉诺塔递归的空间复杂度_暴力递归与动态规划 1.0

    有些暴力递归不能被改成动态规划,因为他本身要求的解空间无法被压缩了. eg:汉诺塔问题,我们必须打印那些步骤. 如果要写一个动态规划,先写成暴力递归的版本,然后模板化修改. 先写递归版本: 不妨记递归 ...

最新文章

  1. Python面向对象编程:入门类和对象
  2. 生命起源之谜:RNA世界假说将迎来终结?
  3. 第一门编程语言的选择无关紧要?
  4. Python爬虫(二)_urllib2的使用
  5. Loadrunner 8.1 下载
  6. python实例[判断操作系统类型]
  7. canvas小程序-快跑程序员
  8. Express--socket.io使用session验证
  9. sklearn保存svm分类模型_机器学习100天-Day1601线性支持向量机分类
  10. Java IO学习7:打印流
  11. springboot - 应用实践(N)使用springboot内置的@Scheduled
  12. 毕业了~(2008-06-04 11:22)
  13. KPI and evaluation decouple verification
  14. Pytorch 学习笔记--to(device)的用法
  15. 一文搞定BP神经网络——从原理到应用(原理篇)
  16. 瑞幸咖啡第四季营收24亿:同比增80.7% 门店总数超6000家
  17. 反应式流 Java 9 Flow实战
  18. 【无标题】有向图的创建、求度和遍历
  19. office官网用户名密码
  20. CSS3: The missing manual 《css3秘笈》笔记+布局、设计优秀资源整理

热门文章

  1. 加州大学研发全柔性汗液传感器,实时监控人体健康
  2. XenDesktop7.12配置StoreFront使用HTTPS
  3. [Linux] 使用openssl实现RSA非对称加密
  4. leetcode- Sqrt(x)
  5. HTML5 audio 标签-在html中定义声音的标签
  6. Springboot整合swagger指南
  7. WEB前端学习笔记01利用纯CSS书写二级水平导航菜单
  8. linux常用命令(1)帮助命令man使用
  9. pyspark reduce代码示例
  10. cordova报错:ANDROID_SDK_ROOT=undefined (recommended setting)