一、步骤

  1. 把问题转化为规模缩小了的同类问题的子问题
  2. 有明确的不需要继续进行递归的条件(base case)
  3. 有当得到了子问题的结果之后的决策过程
  4. 不记录每一个子问题的解

二、实例

2.1 汉诺塔问题

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

2.1.1 思路

不考虑左中右的情况。只考虑需要从源位置from移动到目标位置to,中间暂存的位置是help。那么问题就可以抽象为:

  • 1~i-1:from - > help
  • i :from - > to
  • 1~i-1:help - > to

2.1.2 代码解析

public class Hanoi {
​public static void hanoi(int n) {if (n <= 1) return;// n层汉诺塔,起始位置圆盘全放在left,目标是全部移动到rightfunc(n,"left", "mid", "right");}
​public static void func(int i, String from, String help, String to) {// 当i等于1,说明达到递归终止条件,只剩一个最后圆盘,将它从from移动到to即可if (i == 1){System.out.println("Move 1 from " + from + " to " + to);} else {// 递归过程,和上述思路完全一致,中间i移动的过程打印func(i-1, from, help, to);System.out.println("Move " + i + " from " + from + " to " + to);func(i-1, help, to, from);}}
​public static void main(String[] args) {// 3层汉诺塔的打印示例int n = 3;hanoi(n);}
​
}

2.2 打印字符串的全部子序列

打印一个字符串的全部子序列,包括空字符串

2.2.1 思路

对于字符串中的每个字符,都有两个选择:要or不要,最后就能得到所有情况的子序列

2.2.2 代码解析

public class PrintAllSubsquences {
​public static void printAllSubsquence(String str) {char[] chs = str.toCharArray();process(chs, 0);}
​public static void process(char[] chs, int i) {if(i == chs.length){System.out.println(String.valueOf(chs));return;}// 要当前字符接着走的路process(chs, i+1);char tmp = chs[i];chs[i] = 0;// 不要当前字符接着走的路process(chs, i+1);chs[i] = tmp;}public static void main(String[] args) {String test = "abc";printAllSubsquence(test);}
}

2.3 全排列

打印一个字符串的全排列

2.3.1 思路

从字符串的起点字符开始,将其后续的任一字符与其交换位置则构成一次排列,然后将其恢复继续处理下一位置。(可以利用visited数组优化递归过程,相当于剪枝,避免重复访问造成重复的全排列)

2.3.2 代码解析

public class PrintAllPermutations {
​public static ArrayList<String> Permutation(String str) {// 结果数组ArrayList<String> res = new ArrayList<>();if (str == null || str.length() == 0) return res;char[] chs = str.toCharArray();process(chs, 0, res);return res;
​}
​public static void process(char[] chs, int i, ArrayList<String> res) {// 如果i等于chs的长度,说明完成了一次全排列,将其加入res中if(i == chs.length){res.add(String.valueOf(chs));}// visited存储当前位置是否被访问过,防止重复访问造成重复全排列boolean[] visited = new boolean[26];// i之前的位置是已经选择的位置,i之后的是可以进行选择的位置for (int j = i; j < chs.length; j++) {// 对于i之后的任意位置如果没有被访问过,则可以进行排列if(!visited[chs[j] - 'a']){visited[chs[j] - 'a'] = true;// 排列方式是交换这两个位置完成一次不同排列swap(chs, i, j);// 然后继续处理下一位置process(chs, i+1, res);// 再将之前交换的位置恢复swap(chs, i, j);}
​}}
​public static void swap(char[] chs, int i, int j) {char tmp = chs[i];chs[i] = chs[j];chs[j] = tmp;}
​
}

2.4 玩纸牌

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

【举例】

arr=[1,2,100,4]

开始时,玩家A只能拿走1或4。如果开始时玩家A拿走1,则排列变为[2,100,4],接下来 玩家 B可以拿走2或4,然后继续轮到玩家A...

如果开始时玩家A拿走4,则排列变为[1,2,100],接下来玩家B可以拿走1或100,然后继 续轮到玩家A...

玩家A作为绝顶聪明的人不会先拿4,因为拿4之后,玩家B将拿走100。所以玩家A会先拿1, 让排列变为[2,100,4],接下来玩家B不管怎么选,100都会被玩家 A拿走。玩家A会获胜, 分数为101。所以返回101。

arr=[1,100,2]

开始时,玩家A不管拿1还是2,玩家B作为绝顶聪明的人,都会把100拿走。玩家B会获胜, 分数为100。所以返回100。

2.4.1 思路

主函数返回先手拿牌和后手拿牌中分数高的那个值

  • 作为先手拿牌,他有两种选择:

    • (选择左边+下一次作为后手拿牌)或者(选择右边+下一次作为后手拿牌)中的更大值
    • 当拿到最后一张牌,即L等于R的时候,应该先手拿牌,返回arr[L]作为终止递归的条件
  • 作为后手拿牌,也有两种选择:

    • 如果别人拿走L,那作为先手在L+1和R上进行选择。如果别人拿走R,则作为先手在L和R-1上进行选择,取这两者中的更小值(对方会决定对我最不利的)
    • 当拿到最后一张牌,即L等于R的时候,后手不拿牌,返回0作为终止递归的条件。

2.4.2 代码解析

public class CardsInLine {
​// 暴力递归public static int win1(int[] arr) {if(arr == null || arr.length == 0){return 0;}return Math.max(f(arr, 0, arr.length-1), s(arr, 0, arr.length-1));}
​public static int f(int[] arr, int i, int j) {if(i == j){return arr[i];}return Math.max(arr[i] + s(arr, i+1, j), arr[j] + s(arr, i, j-1));}
​public static int s(int[] arr, int i, int j) {if(i == j) {return 0;}return Math.min(f(arr, i+1, j), f(arr, i, j-1));}
​// 动态优化版本public static int win2(int[] arr) {if(arr == null || arr.length == 0) {return 0;}int[][] f = new int[arr.length][arr.length];int[][] s = new int[arr.length][arr.length];for (int j = 0; j < arr.length; j++) {f[j][j] = arr[j];for (int i = j-1; i >= 0; i--) {f[i][j] = Math.max(arr[i] + s[i+1][j], arr[j] + s[i][j-1]);s[i][j] = Math.min(f[i+1][j], f[i][j-1]);}}return Math.max(f(arr, 0, arr.length-1), s(arr, 0, arr.length-1));}
​public static void main(String[] args) {int[] arr = { 1, 2, 100, 4};System.out.println(win1(arr));System.out.println(win2(arr));
​}
}

2.5 逆序栈

给定一个栈,请逆序这个栈,不能申请额外的数据结构,只能使用递归函数。

2.5.1 思路

首先设计一个递归函数f,f函数可以实现将栈中最底部元素弹出,其余元素覆盖当前栈的功能。

然后设计逆序栈的函数,通过调用f函数进行递归,最后可以实现栈的逆序。

2.5.2 代码解析

public class ReverseStackUsingRecursive {
​public static void reverse(Stack<Integer> stack) {if(stack.isEmpty()) return;else{int i = getAndRemoveLastElement(stack);reverse(stack);stack.push(i);}}
​public static int getAndRemoveLastElement(Stack<Integer> stack) {int result = stack.pop();if(stack.isEmpty()){return result;} else{int last = getAndRemoveLastElement(stack);stack.push(result);return last;}}
​public static void main(String[] args) {Stack<Integer> test = new Stack<Integer>();test.push(1);test.push(2);test.push(3);test.push(4);test.push(5);reverse(test);while (!test.isEmpty()) {System.out.println(test.pop());}
​}
​
}

2.6 数字字符串转化为字母字符串

规定1和A对应、2和B对应、3和C对应...

那么一个数字字符串比如"111",就可以转化为"AAA"、"KA"和"AK"。

给定一个只有数字字符组成的字符串str,返回有多少种转化结果。

2.6.1 思路

假设0到i-1的位置上已经确定,从i位置开始做转化

  • 如果i位置上为‘0‘,返回0,因为0没办法转化为字母
  • 如果i位置上为’3‘ ~ ’9‘,因为字母只有26个,只能将其转化为对应字母,接着尝试下一个位置。
  • 如果i位置上为’1‘ ,有两种选择:
    • i自己作为单独部分,接着尝试i+1~最后
    • i和i+1作为单独部分,接着尝试i+2~最后
  • 如果i位置上为’2‘ ,有两种选择:

    • i自己作为单独部分,接着尝试i+1~最后
    • 如果i+1的值为’0‘ ~ ’6’,可以将i和i+1作为单独部分,接着尝试i+2~最后

2.6.2 代码解析

public class Code06_ConvertToLetterString {
​public static int number(String str) {if (str == null || str.length() == 0) {return 0;}return process(str.toCharArray(), 0);}
​public static int process(char[] chs, int i) {if (i == chs.length) return 1;if (chs[i] == '0') return 0;if (chs[i] == '1'){int res = process(chs, i+1);if (i + 1 < chs.length){res += process(chs, i+2);}return res;}if (chs[i] == '2'){int res = process(chs, i+1);if (i + 1 < chs.length && chs[i+1] >= '0' && chs[i+1] <= '6'){res += process(chs, i+2);}return res;} else{return process(chs, i+1);}}
​public static void main(String[] args) {System.out.println(number("11111"));}
​
}

2.7 装物品

给定两个长度都为N的数组weights和values,weights[i]和values[i]分别代表 i号物品的重量和价值。给定一个正数bag,表示一个载重bag的袋子,你装的物 品不能超过这个重量。返回你能装下最多的价值是多少?

2.7.1 思路

从weights数组中依次拿出一个物品,看bag剩余空间是否能放这个物品,如果能放则有放与不放两种选择。选择结束之后选过的位置不再变化,继续对下一个物品进行选择。

2.7.2 代码解析

public class Code07_Knapsack {
​public static int maxValue1(int[] weights, int[] values, int bag) {return process1(weights, values, 0, bag);}
​/*** 暴力递归* @param weights 重量数组* @param values 价值数组* @param i:i之前的是已经确定的,在i之后进行选择* @param bag:剩余空间* @return*/public static int process1(int[] weights, int[] values, int i, int bag) {// 如果i来到了最后,则没有选择的了,返回0if (i == weights.length){return 0;}// 如果剩余空间大于等于放下的物品,有放与不放两种选择,返回其中更大的那个if (bag >= weights[i]){return Math.max(process1(weights, values, i+1, bag),values[i] + process1(weights, values, i+1, bag-weights[i]));}return 0;}
​// 动态规划版本public static int maxValue2(int[] c, int[] p, int bag) {int[][] dp = new int[c.length+1][bag+1];for(int i = c.length-1; i >= 0 ; i--){for(int j = bag; j >= 0; j--){dp[i][j] = dp[i+1][j];if(j + c[i] <= bag){dp[i][j] = Math.max(dp[i][j], p[i] + dp[i+1][j+c[i]]);}}}return dp[0][0];}
​public static void main(String[] args) {int[] weights = { 3, 2, 4, 7 };int[] values = { 5, 6, 3, 19 };int bag = 11;System.out.println(maxValue1(weights, values, bag));System.out.println(maxValue2(weights, values, bag));}
​
}

有帮助到你的点赞、收藏和关注一下吧

需要更多教程,微信扫码即可

递归算法的总结与应用相关推荐

  1. 算法图解/二分查找/简单查找/选择排序/递归算法/快速排序算法/

    大 O 表示法 大 O 表示法在讨论运行时间时,log 指的都是 log2 大 O 表示法指出了算法有多快,让你能够比较操作数,它指出了算法运行时间的增速,而并非以秒为单位的速度. 大 O 表示法指出 ...

  2. 什么是php递归算法_PHP递归算法(一)

    在前面的文章中,我们为大家介绍了PHP算法系列之<PHP随机取一算法>和<PHP冒泡排序算法>,需要的朋友可以了解学习.本篇文章我们将继续为大家带来常见的PHP算法,即PHP递 ...

  3. 漫画:5分钟弄懂分治算法!它和递归算法的关系!

    分治顾名思义"分而治之",英文的意思翻译为"分割并征服". 分治思想,简而言之就是将原问题分解成与"原问题相同但是规模更小"的子问题,并可以 ...

  4. 算法笔记-递归算法、递归排序、递归的时间复杂度、master公式(也叫主方法)

    1. 递归排序题 通过递归算法来获取一个数组中的最大值 2. 算法思路 采用递归的思路来进行解题,那么肯定是需要自己调用自己的,这也就是递归,于是考虑怎么自己调用自己呢?我们可以采用二分法来制造递归的 ...

  5. 后序遍历的非递归算法python_二叉树后序遍历(递归与非递归)算法C语言实现...

    二叉树后序遍历的实现思想是:从根节点出发,依次遍历各节点的左右子树,直到当前节点左右子树遍历完成后,才访问该节点元素. 图 1 二叉树 如图 1 中,对此二叉树进行后序遍历的操作过程为: 从根节点 1 ...

  6. PHP开发之递归算法的三种实现方法

    递归算法对于任何一个编程人员来说,应该都不陌生.因为递归这个概念,无论是在PHP语言还是Java等其他编程语言中,都是大多数算法的灵魂.对于PHP新手来说,递归算法的实现原理可能不容易理解.但是只要你 ...

  7. 汉诺塔递归与非递归算法

    问题描述: 在印度,有这么一个古老的传说:在世界中心贝拿勒斯(在印度北部)的圣庙里,一块黄铜板上插着三根宝石针.印度教的主神梵天在创造世界的时候,在其中一根针上从下到上地穿好了由大到小的64片金片,这 ...

  8. 2.2基本算法之递归和自调用函数_你为什么学不会递归?读完这篇文章轻松理解递归算法...

    对于很多编程初学者来说,递归算法是学习语言的最大障碍之一.很多人也是半懂不懂,结果学到很深的境地也会因为自己基础不好,导致发展太慢. 可能也有一大部分人知道递归,也能看的懂递归,但在实际做题过程中,却 ...

  9. 实习二 栈、队列和递归算法设计 (题目:停车场管理 )

    一.需求分析 1.每一组输入数据包括:汽车"到达"或"离去"信息.汽车牌照号码以 及到达或离去的时刻. 2.输出信息:若是车辆到达,则输出汽车在停车场内或便道上 ...

  10. 递归算法转换为非递归算法的技巧

    递归算法转换为非递归算法的技巧 递归函数具有很好的可读性和可维护性,但是大部分情况下程序效率不如非递归函数,所以在程序设计中一般喜欢先用递归解决问题,在保证方法正确的前提下再转换为非递归函数以提高效率 ...

最新文章

  1. 【实用总结】DOM节点className操作
  2. 关于hive中.conf配置文档中sink为avro的端口问题
  3. 牛客 - 共鸣问题(贪心+思维)
  4. Essential MSBuild: .NET 工具生成引擎概述
  5. 【软件开发底层知识修炼】十 链接器-main函数不是第一个被执行的函数
  6. oracle过程包保存乱码_这些火遍网络的哆啦A梦表情包,你知道出处吗?
  7. SQLServer-Error Log
  8. UVa10006 Carmichael Numbers【素数判定+快速模幂】
  9. angular.js 验证码注册登录
  10. Java 常用工具类 - 校验银行卡号 BankCardUtils
  11. 《代码整洁之道》第14章 逐步改进 的代码片段
  12. matlab pdetool解热传导方程,传热学与应用(李晓炜)基于pdetool的热传导数值计算.pdf...
  13. select默认选中
  14. 【esp32lvgl】-2.1 # esp32移植lvgl7驱动st7789屏幕(ESP-IDF框架)
  15. 教你三步实现CDH到星环TDH的平滑迁移
  16. 概率论中PDF、PMF和CDF的区别与联系
  17. 【牛客网专项练习题】
  18. 商务部关于网上交易的指导意见(暂行)
  19. 安卓模拟器登录微信自动化测试最佳实践
  20. PS制作红色拟物化时钟icon图标

热门文章

  1. 蔚来宣布再次完成1亿美元可转债融资
  2. 中国移动咪咕公司:打造五新体验 做5G时代内容的聚合者与生产者
  3. 华为nova 5z即将亮相:麒麟810+3200万人像超级夜景
  4. 三星5G手机全球销量200万台:年底将翻番
  5. OLED电视出现烧屏问题 LG电子被判赔偿消费者16万澳元
  6. 索尼Xperia 2带壳渲染图曝光:外形依然很索尼
  7. 听着三只松鼠上市的钟声,罗永浩流下了悔恨的泪水
  8. 运行了9年的QQ邮箱经典功能 终于还是迎来了终止服务
  9. iOS12.3正式版发布 iOS13亮相进入倒计时
  10. DOM基础、定时器、BOM基础