如何才能轻而易举的写出递归函数

先说下我的学习方式吧

首先,我想说下我的学习方式,每个人的学习方式都是不同的,有的人悟性比较高,所以学东西比较快,但也有一部分人,学东西比较慢,哈哈,我就是那学东西比较慢的那一部分人。但我也不气馁,认真踏实的学就可以了,千万不能浮躁。
所以在我学习算法的过程中,首先要搞懂这个技术是来解决什么问题的,这是很重要的一步,其次如何使用,最后它的原理是什么。走好这三步我觉得这个技术你也就学会了。

递归

首先第一步,递归这个算法是用来解决什么问题的或者主要用于什么方向的。在我目前的理解,递归主要是将一个大问题分解成小问题,最后各个小问题的解再进行合并。这就是递归的主要作用。

注意两个技巧来编写递归函数:

  • 1.思考这个问题能否分解成小问题
  • 2.递归函数的编写在我看来最重要的是定义递归函数的功能,不要太纠结递归函数是怎么运行的,这么思考会陷入死胡同的,我之前也是纠结这块,弄得头疼。

做几道题目吧(leetcode 343,leetcode 337)

leetcode 343:给定一个正整数 n,将其拆分为至少两个正整数的和,并使这些整数的乘积最大化。 返回你可以获得的最大乘积。

实例:输入: 10
输出: 36
解释: 10 = 3 + 3 + 4, 3 × 3 × 4 = 36。

首先如何思考题目:说至少拆分成两个数,那么拆分出来的两个数是否又可以进行拆分?
就好比10拆分成10=4+6,那么6是否可以拆分成4+2?是不是有一点激动,是不是很像递归,将一个大问题拆分成一个个小问题。

那么知道了方法,那思路是怎么的呢?我们可以将一个数从1进行拆分,一直到n-1。打个比方吧,比如10,我们一个个拆分成【1,9】,【2,8】,【3,7】…【9,1】,那么在拆分的过程中,比如拆分成【1,9】,9是否又可以进行拆分?是重复的一个过程,但就是这个过程我们定义成了递归函数,所以递归函数的功能就是给你一个数返回它的乘积最大化结果。
那我们现在就来定义递归函数的功能:

//递归函数的功能定义非常重要: 
public int help(int num){   //返回一个传递的正整数num的乘积最大化得结果
	if(num==2){    //这是临界条件,大家可以动下脑筋就能思考出来了,当时2的时候,只能拆分成1+1
		return 1;
	}
	int res=0;
	for(int i=1;i<num;i++){
		//将num拆分成i和num-i
		int a=i*help(num-i);//num-i是不是又可以拆分成多个数字之和,
							//那我们在将num-i传递到递归函数中即可,不要忘记递归函数的功能是返回一个
							//正整数的乘积最大化的结果,所以我们只需要从i遍历到num-1,求出最大值即可
		int b=i*(num-i); //这边代码是什么意思呢?比如10分成【1,9】,9的确可以再分,但我不分9了,
		 			    //直接两个数相乘求乘积也可以吧,这题的总体思想就是递归,递归中也包含分与不分两个方面
		res=Math.max(res,Math.max(a,b)); //比较a和b两个最大值,再和res比较得出res结果
	}
	return res;
}

上面那道题就完成了,但是写成那样,时间复杂度比较高,应该不可能通过所有测试用例,所以我们要利用HashMap<Integer,Integer> 来记录一个正整数的最大化乘积的值,避免重复计算。这种方式就是备忘录方法。

HashMap<Integer,Integer> map=new HashMap<>();
public int help(int num){   
	if(num==2){    
		return 1;
	}
	//主要添加了下面三行代码,你们想要,我求出了10的最大化,
	//是不是要进行保存起来,因为10的最大化是一个固定值,不是变化的,所以当再用到10这个正整数的时候
	//就无须再进行计算了,直接返回他的结果就可以了
	if(map.containsKey(num)){
            return map.get(num);
    }
	int res=0;
	for(int i=1;i<num;i++){
		//将num拆分成i和num-i
		int a=i*help(num-i);
							
		int b=i*(num-i); 
		res=Math.max(res,Math.max(a,b)); 
	}
	//将num这个正整数的最大化乘积res进行保存
	map.put(num,res);
	return res;
}

到目前为止,这道题才算是解决了。

接着再来一题,

leetcode 337:在上次打劫完一条街道之后和一圈房屋后,小偷又发现了一个新的可行窃的地区。这个地区只有一个入口,我们称之为“根”。 除了“根”之外,每栋房子有且只有一个“父“房子与之相连。一番侦察之后,聪明的小偷意识到“这个地方的所有房屋的排列类似于一棵二叉树”。 如果两个直接相连的房子在同一天晚上被打劫,房屋将自动报警。

计算在不触动警报的情况下,小偷一晚能够盗取的最高金额。
在这里插入图片描述
在这里插入图片描述
这一题如何思考呢?在我认为递归最要紧的就是定义函数功能,这一题的递归函数功能是什么呢?那就是给你一个二叉树中的任意一个节点,将它当做根节点,返回此节点的最大盗取金额。比如上一张图片中的4,如果传递的节点是4,那我们要返回的就是以4为根节点,小偷在以4为根节点的树上,能盗取的最大金额是多少,当然为了便于理解,3,5,1节点可以舍弃不看。有了这样的思路,我们来编写程序。

public int rob(TreeNode root){ //定义函数功能:以某个节点为根,返回小偷能盗取的最大值
         if(root==null){  //当节点为空,返回0,初始条件
             return 0;
         }
         //因为小偷不能连续盗窃,所以如果偷根节点,那么根节点的左右子树就不能偷所以分成两类
         //一类:根节点+根节点的孙子节点  另一类:根节点的儿子节点
         int left=0;   //计算以根节点的左孙子节点为根节点,小偷能盗取的最大值
         int right=0;  //计算以根节点的右孙子节点为根节点,小偷能盗取的最大值
         //计算根节点和根节点左右子树的子树
         if(root.left!=null){
             left=rob(root.left.left)+rob(root.left.right);
         }
         if(root.right!=null){
             right=rob(root.right.right)+rob(root.right.left);
         }
         int sum1=root.val+left+right;//维护的就是根节点加根节点的左右孙子节点
         int sum2=rob(root.left)+rob(root.right);//维护的就是根节点的左右儿子节点能获取的最大值
         return sum1>sum2?sum1:sum2; //返回最大值
     }

再次强调,必须要定义好递归函数的功能,**rob(root.left.left)**返回的就是以根节点的左孩子的左孩子为根节点小偷能盗取的最大值,定义递归函数功能尤其重要。
画张图就能更好地理解了:
在这里插入图片描述
不过上面的递归时间复杂度是有点高的,大家可以想想,以某个节点为根节点,那么能盗取的最大金额是固定的,所以我们可以维护一个HashMap<TreeNode,Integer>来做备忘录。代码就不写了,很简单,和第一题的备忘录几乎一样,大家可以动手试试。

总结:

递归函数在我看来最重要的是要定义递归函数的功能,这个最为重要,其次就是要明白递归是一个不断重复的子过程。不过我们也可以在此基础上画图来更加深入地理解递归。举个简单的例子,如何用递归求一个数组的最大值?我们可以将数组一分为2,左边求最大值,右边求最大值,然后比较返回,此时的递归函数功能是啥?就是给你一个数组中的指定范围,求出指定范围的数组最大值。我们可以举个简单例子,比如只有4个元素的数组,可以进行画图来更加深入的理解上面的递归思想。不过话说回来,每个人都有每个人的看法,大家可以多思考,可以按照文章中提出的方法多写几道递归的题目,总有一天会让你感受到递归的思想。递归是很强大的,比如我们使用的排序,例如归并排序,快速排序都离不开递归。

这边可以稍微提一下两个排序的思想:归并排序就是将数组一分为2,左边先排好序,右边排好序,然后再合并。那归并排序的递归函数功能是什么呢?就是排序。我们要做的就是先将数组一分为2,左半部分调用递归有序,右半部分调用递归有序,我们最后要做的就是将两个有序数组合并成一个有序数组。

那么快速排序的怎么理解呢?快速排序涉及到荷兰国旗问题,就是随机选取一个数,将数组中小于这个数的数字放在左边,大于这个数的数字放在右边,接着再对左右两边继续递归。这两个排序加堆排序我也会进行整理总结,毕竟他们在面试中经常被提问,还是比较重要的。

这是我第一次写博客,可能有些语句不通顺或者逻辑不通,还请大家见谅,也希望大家能多多留言,我们一起进步。

  • 5
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值