Java高频面试专题合集解析:
当然在这还有更多整理总结的Java进阶学习笔记和面试题未展示,其中囊括了Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构资料和完整的Java架构学习进阶导图!
更多Java架构进阶资料展示
输入:
4
/ \
2 7
/ \ / \
1 3 6 9
输出:
4
/ \
7 2
/ \ / \
9 6 3 1
我们按照以上递归解题的三板斧来:
「1. 定义函数功能」
函数功能(即这个递归原问题是),给出一颗树,然后翻转它,所以,函数可以定义为:
//翻转一颗二叉树
public TreeNode invertTree(TreeNode root) {
}
/**
* 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;
总结
其他的内容都可以按照路线图里面整理出来的知识点逐一去熟悉,学习,消化,不建议你去看书学习,最好是多看一些视频,把不懂地方反复看,学习了一节视频内容第二天一定要去复习,并总结成思维导图,形成树状知识网络结构,方便日后复习。
这里还有一份很不错的《Java基础核心总结笔记》,特意跟大家分享出来
目录:
部分内容截图:
turn 1;
总结
其他的内容都可以按照路线图里面整理出来的知识点逐一去熟悉,学习,消化,不建议你去看书学习,最好是多看一些视频,把不懂地方反复看,学习了一节视频内容第二天一定要去复习,并总结成思维导图,形成树状知识网络结构,方便日后复习。
这里还有一份很不错的《Java基础核心总结笔记》,特意跟大家分享出来
目录:
[外链图片转存中…(img-NYSSgRs9-1715111350290)]
部分内容截图:
[外链图片转存中…(img-lBYaUgpB-1715111350291)]
[外链图片转存中…(img-KnrcPaiK-1715111350291)]