此篇文章与大家分享递归,搜索与回溯算法关于递归的专题
如果有不足的或者错误的请您指出!
目录
- 1.什么时候使用递归
- 2.汉诺塔
- 2.1解析
- 2.2题解
- 3.合并两个有序链表
- 3.1解析
- 3.2题解
- 4.翻转链表
- 4.1解析
- 4.2题解
- 5.两两交换链表中的节点
- 5.1解析
- 5.2解析
- 6.快速幂
- 6.1解析
- 6.2题解
1.什么时候使用递归
在遇到一道题的时候,如果我们发现一下几种情况,那么说明这道题可以使用递归来做
(1)这个问题可以被拆分成若干个小问题,且这些小问题和原来的主问题,有着一样的解决方法
(2)我们可以通过更小的子问题的解推出主问题的解
(3)当规模足够小的时候,可以直接求出答案
2.汉诺塔
题目:汉诺塔
2.1解析
如图所示,我们要将A柱的三个盘子,借助B柱,转移到C柱
那么我们的就需要分成三个大的步骤:
(1)将A柱的上面两个盘子,借助C柱,转移到B柱
(2)将A柱剩下的最大的盘子,放到C柱
(3)将B柱的两个盘子,借助A柱,转移到C柱
此时我们就能很明显的发现所谓的子问题,就是上面的(1)和(3),几个盘子,借助哪个盘子,转移到哪个盘子的问题
当我们仅有一个盘子的时候,那么直接将这个盘子移到C柱即可,即n = 1的时候,直接移动.这就是我们上面所说的,规模足够小的时候,可以直接得出答案
上面就是递归的精髓所在,我们完全可以将我们的递归函数看成 一个黑盒子,我们有理由完全相信这个黑盒子能够把我们的子问题解决,此外再找到规模足够小的时候,直接解决问题,作为递归的出口即可
2.2题解
class Solution {
private void hanoi(List<Integer> pos1, List<Integer> pos2,List<Integer> pos3,int n) {
if(n == 1) {
pos3.add(pos1.remove(pos1.size()-1));
return;
}
//将pos1的n-1盘子,借助C,移动到B
hanoi(pos1,pos3,pos2,n-1);
//将pos1剩下的一个盘子,移动到pos3
pos3.add(pos1.remove(pos1.size()-1));
//将pos2的n-1个盘子,借助pos1,移动到pos3
hanoi(pos2,pos1,pos3,n-1);
}
public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {
hanoi(A,B,C,A.size());//主问题,将n个盘子,从A,借助B,移动到C
}
}
3.合并两个有序链表
题目:合并两个有序链表
3.1解析
如图所示,如果我们要合并上述两个链表,那么就要分成3个步骤
(1)比较出两个链表头节点的大小,哪个小?记为node1
(2)将node1后面的节点形成的子链表,与另一个链表合并成有序链表,并将合并完后的头结点返回
(3)将node1的next指向(2)操作返回的头结点
此时我们就能很好的找到所谓的子问题,就是合并两个链表形成有序链表,就可以使用递归来做
那递归的出口是什么呢??
当我们其中的一条链表为空的时候,那么就直接返回第二个链表的头节点即可
3.2题解
class Solution {
public ListNode mergeTwoLists(ListNode list1, ListNode list2) {
if(list1 == null) {
return list2;
}
if(list2 == null) {
return list1;
}
ListNode newHead = null;
if(list1.val > list2.val){
newHead = list2;
newHead.next = mergeTwoLists(list2.next,list1);
}else{
newHead = list1;
newHead.next = mergeTwoLists(list1.next,list2);
}
return newHead;
}
}
4.翻转链表
题目:翻转链表
4.1解析
如图所示,我们要将上述链表翻转,就需要分成3步
(1)将除了头结点外,其余节点组成的子链表翻转,并将翻转完后的头结点返回
(2)将(1)中原来的头结点的next的next指向原来的头节点
(3)将原来头结点的next指向null
那么此时的子问题就是:将链表除了头结点外的部分进行翻转,并返回翻转完后的头结点,就可以使用递归来做
递归的出口是什么??当只有一个节点的时候,就直接返回这个节点即可,即当head.next == null时,返回head
4.2题解
class Solution {
public ListNode reverseList(ListNode head) {
if(head == null || head.next == null) {
return head;
}
ListNode newHead = reverseList(head.next);
head.next.next = head;
head.next = null;
return newHead;
}
}
5.两两交换链表中的节点
题目:两两交换链表中的节点
5.1解析
有了前两题的铺垫,这道题也是类似
要两两翻转链表中的节点,分为3步
(1)将第二个2个节点后的链表两两交换,返回交换完后的头结点
(2)将前两个加点交换
(3)将交换完后的节点与(1)返回的头结点连接
那么此时子问题就是 两两交换链表中的节点,就可以用递归来做
递归的出口就是:当只有一个节点或者节点为空的时候,直接返回这个节点即可
5.2解析
class Solution {
public ListNode swapPairs(ListNode head) {
if(head == null || head.next == null) {
return head;
}
ListNode newHead = swapPairs(head.next.next);
ListNode next = head.next;
next.next = head;
head.next = newHead;
return next;
}
}
6.快速幂
题目:快速幂
6.1解析
快速幂是一种用于快速计算x 的 n次幂的算法
假设我们现在计算的是2 ^ 32
那么快速幂的思想就是
此时原来通过暴力解法,需要循环32次的计算,现在只需要5次即可
那么我们将问题拆分出来,要想求2 ^ 32 ,就要先求 2^16,即要先求 2 ^ (n/2)…
那么子问题就是,求出2的 n 次方 ,就可以使用递归来做
此时当n = 0的时候,就是递归的出口
但是如果n不是偶数,是奇数怎么办??
我们只需要提出其中的一个2即可
6.2题解
class Solution {
private double pow(double x,long n) {
if(n == 0) {
return 1;
}
double tmp = pow(x,n/2);
return n % 2 == 1 ? tmp * tmp * x : tmp * tmp;
}
public double myPow(double x, int nn) {
long n = (long)nn;//防止越界
return n < 0 ? 1.0 / pow(x,-n) : pow(x,n);//处理n 是负数的问题
}
}