一、整体思路
不用一层层深入下去看,而是把后面的部分理解成“已经递归完的结果”。比如说二叉树的题,就把问题看成根节点,左子树递归完的结果和右子树递归完的结果。不要再深入左子树、右子树了。

如果深入看的话,是这样的:

再比如翻转链表的题

递归出口
对于二叉树来说,只分析“空树”就可以作为递归出口(root==null)。但是对于翻转链表这样的普通递归,

二、递归中有循环
递归中有循环的话,图是这样画的,有几个循环就有几个结点(参考八皇后问题、迷宫问题)

三、反转链表—— 这个人的想法和我一样
-
问题:逆序打印一个数组
-
递推公式:
假设令F(n)=逆序遍历长度为n的数组
那么F(n)= 打印数组中下标为n的元素 + F(n-1)
-
终止条件:
if (n <0) return ;
-
递归代码:
public void Print(int[] nums,intn){
if(n<0) return;
System.out.println(nums[n]);
Print(nums,n-1);
}
到这里,不知道大家对写递归有没有一些理解了。其实写递归不能总想着去把递归平铺展开,这样脑子里就会循环,一层一层往下调,然后再一层一层返回,试图想搞清楚计算机每一步都是怎么执行的,这样就很容易被绕进去。对于递归代码,这种试图想清楚整个递和归过程的做法,实际上是进入了一个思维误区。只要找到递推公式,我们就能很轻松地写出递归代码。
到这里,我想进一步跟大家说明我这个思路是比较能够容易出代码的,那么就树的遍历问题来和大家讲。递归总是和树分不开,其中,最典型的便是树的遍历问题。刚开始学的时候,不知道大家是怎么理解先/中/后序遍历的递归写法的,这里我提供我的思路供参考,以前序遍历为例
-
问题:二叉树的先序遍历
-
递推公式:
令F(Root)为问题:遍历以Root为根节点的二叉树,
令F(Root.left)为问题:遍历以F(Root.left)为根节点的二叉树
令F(Root.right)为问题:遍历以F(Root.right)为根节点的二叉树
那么其递推公式为:
F(Root)=遍历Root节点+F(Root.left)+F(Root.right)
-
递归代码:
public void preOrder(TreeNode node){
if(node==null) return;
System.out.println(node.val);
preOrder(node.left);
preOrder(node.righr);
}
具体点讲,如果一个问题 A 可以分解为若干子问题 B、C、D,你可以假设子问题 B、C、D 已经解决,在此基础上思考如何解决问题 A。而且,你只需要思考问题 A 与子问题 B、C、D 两层之间的关系即可,不需要一层一层往下思考子问题与子子问题,子子问题与子子子问题之间的关系(也就是说,递归只能考虑当前层和下一层的关系,不能继续往下深入)。我们需要屏蔽掉递归细节,理解为完成了某种功能的形式化描述即可。
好了,那我们来分析这个题
令F(node)为问题:反转以node为头节点的单向链表;
一般,我们需要考虑F(n)和F(n-1)的关系,那么这里,如果n代表以node为头节点的单向链表,那么n-1就代表以node.next为头节点的单向链表.
所以,我们令F(node.next)为问题:反转以node.next为头节点的单向链表;
那么,F(node)和F(node.next)之间的关系是?这里我们来简单画个图,假设我们反转3个节点的链表:
1 -> 2 -> 3
那么,F(node=1)=F(node=2)+?
这里假设子问题F(node=2)已经解决,那么我们如何解决F(node=1):
很明显,我们需要反转node=2和node=1, 即 node.next.next=node; 同时 node.next=null;
所以,这个问题就可以是:F(node=1)=F(node=2)+ 反转node=2和node=1
-
递归代码:
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) {
return head;
}
ListNode node = reverseList(head.next);
head.next.next = head;
head.next = null;
return node; //按上面的例子,F(node=1)和F(node=2)它俩反转后的头节点是同一个
}
四、递归结果

思路:先求中间元素nums[mid],然后让nums[mid]和nums[mid-1]和nums[mid+1]比较。
排除nums[mid]本身就是要求的结果以外,有两种情况:
-
nums[mid]==nums[mid-1]
-
nums[mid]==nums[mid+1]
此外,因为每个元素都会出现两次,只有一个元素会出现一次,那么nums一定有奇数个元素。but如果nums是11元素,那么mid的两边各5个元素,是奇数个;如果nums是9个元素,那么mid的两边各4个元素是偶数个。所以又有两种情况:
-
mid的两边是奇数个元素
-
mid的两边是偶数个元素
排列组合,一共四种情况!思路并不是这个题的关键
class Solution {
public:
int result;
int singleNonDuplicate(vector<int>& nums) {
//特殊情况
if(nums.size()==1) return nums[0];
deep(0,nums.size()-1,nums);
return result;
}
void deep(int low, int high,vector<int>& nums){
//递归出口
if(low==high) {
result=nums[low];
return;
}
int mid=(high+low)/2;
//先判断num[mid]是不是只出现一次的数字
if(nums[mid]!=nums[mid-1]&&nums[mid]!=nums[mid+1]){
result = nums[mid];
return;
//和右边的相同,并且右边是偶数个。那么一定在右边
}else if(nums[mid]==nums[mid+1]&&((high-low)/2)%2==0){
deep(mid+2,high,nums);
//和右边的相同,并且右边是奇数个,那么一定在左边
}else if(nums[mid]==nums[mid+1]&&((high-low)/2)%2==1){
deep(low,mid-1,nums);
//和左边相同,并且左边是偶数个,那么一定在左边
}else if(nums[mid]==nums[mid-1]&&((high-low)/2)%2==0){
deep(low,mid-2,nums);
//和左边相同,并且左边是奇数个,那么一定在右边
}else if(nums[mid]==nums[mid-1]&&((high-low)/2)%2==1){
deep(mid+1,high,nums);
}
}
};
我要说的关键是,这个题只需要找出“只会出现一次的数字”,不需要把递归的返回值设置成int,只需要维护一个全局变量就行了。

如果非要把递归返回值写成int类型的
