Java最新程序员必备的基本算法:递归详解(1),java面试视频软件

最后

我想问下大家当初选择做程序员的初衷是什么?有思考过这个问题吗?高薪?热爱?

既然入了这行就应该知道,这个行业是靠本事吃饭的,你想要拿高薪没有问题,请好好磨练自己的技术,不要抱怨。有的人通过培训可以让自己成长,有些人可以通过自律强大的自学能力成长,如果你两者都不占,还怎么拿高薪?

架构师是很多程序员的职业目标,一个好的架构师是不愁所谓的35岁高龄门槛的,到了那个时候,照样大把的企业挖他。为什么很多人想进阿里巴巴,无非不是福利待遇好以及优质的人脉资源,这对个人职业发展是有非常大帮助的。

如果你也想成为一名好的架构师,那或许这份Java核心架构笔记你需要阅读阅读,希望能够对你的职业发展有所帮助。

中高级开发必知必会:

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

/**

  • Definition for a binary tree node.

  • public class TreeNode {

  • int val;
    
  • TreeNode left;
    
  • TreeNode right;
    
  • TreeNode(int x) { val = x; }
    
  • }

*/

复制代码

2.寻找递归终止条件

这棵树什么时候不用翻转呢?当然是当前节点为null或者当前节点为叶子节点的时候啦。因此,加上终止条件就是:

//翻转一颗二叉树

public TreeNode invertTree(TreeNode root) {

if(root==null || (root.left ==null && root.right ==null)){

return root;

}

}

复制代码

3. 递推函数的等价关系式

原问题之你要翻转一颗树,是不是可以拆分为子问题,分别翻转它的左子树和右子树?子问题之翻转它的左子树,是不是又可以拆分为,翻转它左子树的左子树以及它左子树的右子树?然后一直翻转到叶子节点为止。嗯,看图理解一下咯~

首先,你要翻转根节点为4的树,就需要翻转它的左子树(根节点为2)和右子树(根节点为7)。这就是递归的的过程啦

然后呢,根节点为2的树,不是叶子节点,你需要继续翻转它的左子树(根节点为1)和右子树(根节点为3)。因为节点1和3都是叶子节点了,所以就返回啦。这也是递归的的过程~

同理,根节点为7的树,也不是叶子节点,你需要翻转它的左子树(根节点为6)和右子树(根节点为9)。因为节点6和9都是叶子节点了,所以也返回啦。

左子树(根节点为2)和右子树(根节点为7)都被翻转完后,这几个步骤就归来,即递归的归过程,翻转树的任务就完成了~

显然,递推关系式就是:

invertTree(root)= invertTree(root.left) + invertTree(root.right);

复制代码

于是,很容易可以得出以下代码:

//翻转一颗二叉树

public TreeNode invertTree(TreeNode root) {

if(root==null || (root.left ==null && root.right ==null){

return root;

}

//翻转左子树

TreeNode left = invertTree(root.left);

//翻转右子树

TreeNode right= invertTree(root.right);

}

复制代码

这里代码有个地方需要注意,翻转完一棵树的左右子树,还要交换它左右子树的引用位置。

root.left = right;

root.right = left;

复制代码

因此,leetcode这个递归经典题目的终极解决代码如下:

class Solution {

public TreeNode invertTree(TreeNode root) {

if(root==null || (root.left ==null && root.right ==null)){

return root;

}

//翻转左子树

TreeNode left = invertTree(root.left);

//翻转右子树

TreeNode right= invertTree(root.right);

//左右子树交换位置~

root.left = right;

root.right = left;

return root;

}

}

复制代码

拿终极解决代码去leetcode提交一下,通过啦~

递归存在的问题

  • 递归调用层级太多,导致栈溢出问题

  • 递归重复计算,导致效率低下

栈溢出问题

  • 每一次函数调用在内存栈中分配空间,而每个进程的栈容量是有限的。

  • 当递归调用的层级太多时,就会超出栈的容量,从而导致调用栈溢出。

  • 其实,我们在前面小节也讨论了,递归过程类似于出栈入栈,如果递归次数过多,栈的深度就需要越深,最后栈容量真的不够咯

代码例子如下:

/**

  • 递归栈溢出测试

*/

public class RecursionTest {

public static void main(String[] args) {

sum(50000);

}

private static int sum(int n) {

if (n <= 1) {

return 1;

}

return sum(n - 1) + n;

}

}

复制代码

运行结果:

Exception in thread “main” java.lang.StackOverflowError

at recursion.RecursionTest.sum(RecursionTest.java:13)

复制代码

怎么解决这个栈溢出问题?首先需要优化一下你的递归,真的需要递归调用这么多次嘛?如果真的需要,先稍微调大JVM的栈空间内存,如果还是不行,那就需要弃用递归,优化为其他方案咯~

重复计算,导致程序效率低下

我们再来看一道经典的青蛙跳阶问题:一只青蛙一次可以跳上1级台阶,也可以跳上2级台阶。求该青蛙跳上一个 n 级的台阶总共有多少种跳法。

绝大多数读者朋友,很容易就想到以下递归代码去解决:

class Solution {

public int numWays(int n) {

if (n == 0){

return 1;

}

if(n <= 2){

return n;

}

return numWays(n-1) + numWays(n-2);

}

}

复制代码

但是呢,去leetcode提交一下,就有问题啦,超出时间限制了

为什么超时了呢?递归耗时在哪里呢?先画出递归树看看:

  • 要计算原问题 f(10),就需要先计算出子问题 f(9) 和 f(8)

  • 然后要计算 f(9),又要先算出子问题 f(8) 和 f(7),以此类推。

  • 一直到 f(2) 和 f(1),递归树才终止。

我们先来看看这个递归的时间复杂度吧,递归时间复杂度 = 解决一个子问题时间*子问题个数

  • 一个子问题时间 = f(n-1)+f(n-2),也就是一个加法的操作,所以复杂度是 O(1)

  • 问题个数 = 递归树节点的总数,递归树的总结点 = 2n-1,所以是复杂度**O(2n)**。

因此,青蛙跳阶,递归解法的时间复杂度 = O(1) * O(2^n) = O(2^n),就是指数级别的,爆炸增长的,如果n比较大的话,超时很正常的了

回过头来,你仔细观察这颗递归树,你会发现存在大量重复计算,比如f(8)被计算了两次,f(7)被重复计算了3次…所以这个递归算法低效的原因,就是存在大量的重复计算!

那么,怎么解决这个问题呢?

既然存在大量重复计算,那么我们可以先把计算好的答案存下来,即造一个备忘录,等到下次需要的话,先去备忘录查一下,如果有,就直接取就好了,备忘录没有才再计算,那就可以省去重新重复计算的耗时啦!这就是带备忘录的解法

我们来看一下带备忘录的递归解法吧~

一般使用一个数组或者一个哈希map充当这个备忘录

假设f(10)求解加上备忘录,我们再来画一下递归树:

第一步,f(10)= f(9) + f(8),f(9) 和f(8)都需要计算出来,然后再加到备忘录中,如下:

第二步, f(9) = f(8)+ f(7),f(8)= f(7)+ f(6), 因为 f(8) 已经在备忘录中啦,所以可以省掉,f(7),f(6)都需要计算出来,加到备忘录中~

第三步, f(8) = f(7)+ f(6),发现f(8),f(7),f(6)全部都在备忘录上了,所以都可以剪掉。

所以呢,用了备忘录递归算法,递归树变成光秃秃的树干咯,如下:

带「备忘录」的递归算法,子问题个数=树节点数=n,解决一个子问题还是O(1),所以带「备忘录」的递归算法的时间复杂度是O(n)。接下来呢,我们用带「备忘录」的递归算法去撸代码,解决这个青蛙跳阶问题的超时问题咯~,代码如下:

public class Solution {

//使用哈希map,充当备忘录的作用

Map<Integer, Integer> tempMap = new HashMap();

public int numWays(int n) {

// n = 0 也算1种

if (n == 0) {

return 1;

}

if (n <= 2) {

return n;

}

//先判断有没计算过,即看看备忘录有没有

if (tempMap.containsKey(n)) {

//备忘录有,即计算过,直接返回

return tempMap.get(n);

} else {

// 备忘录没有,即没有计算过,执行递归计算,并且把结果保存到备忘录map中,对1000000007取余(这个是leetcode题目规定的)

tempMap.put(n, (numWays(n - 1) + numWays(n - 2)) % 1000000007);

return tempMap.get(n);

}

}

}

复制代码

去leetcode提交一下,如图,稳了:


还有没有其他方案解决这个问题呢?只有带备忘录的递归解法?其实吧,还可以用动态规划去解决
动态规划算法思想怎么解题?我们下期继续~ 谢谢阅读~

写在最后

学习技术是一条慢长而艰苦的道路,不能靠一时激情,也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后再分享的一些BATJ等大厂20、21年的面试题,把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

Mybatis面试专题

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

MySQL面试专题

蚂蚁金服三面直击面试官的Redis三连,Redis面试复习大纲在手,不慌

并发编程面试专题

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

也不是熬几天几夜就能学好的,必须养成平时努力学习的习惯。所以:贵在坚持!

最后再分享的一些BATJ等大厂20、21年的面试题,把这些技术点整理成了视频和PDF(实际上比预期多花了不少精力),包含知识脉络 + 诸多细节,由于篇幅有限,上面只是以图片的形式给大家展示一部分。

[外链图片转存中…(img-5UnEIXwo-1715440525029)]

Mybatis面试专题

[外链图片转存中…(img-m67B3SFB-1715440525029)]

MySQL面试专题

[外链图片转存中…(img-ePY0Ds1M-1715440525030)]

并发编程面试专题

本文已被CODING开源项目:【一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码】收录

需要这份系统化的资料的朋友,可以点击这里获取

  • 18
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值