递归、搜索与回溯算法
- 1. 汉诺塔
- 2. 合并两个有序链表
- 3. 反转链表
- 4. 两两交换链表中的节点
- 5. Pow(x,n)-快速幂
1. 汉诺塔
题目链接: 面试题 08.06. 汉诺塔问题
解题思路:
- 首先观察有一个、两个、三个盘子时的情况,手动去挪
- 不难发现,大致都是先将上面n-1个盘子借助C的辅助,挪动到B柱子上;再将剩下的那个盘子从A柱子挪动C柱子;最后将B柱子上的n-1个盘子通过A的辅助,挪动到C柱子上
实现代码:
class Solution {public void hanota(List<Integer> A, List<Integer> B, List<Integer> C) {move(A, B, C, A.size());}private void move(List<Integer> start, List<Integer> temp, List<Integer> end, int n) {if(n == 1) {move(start, end);return;}move(start, end, temp, n-1);move(start, end);move(temp, start, end, n-1);}private void move(List<Integer> start, List<Integer> end) {int val = start.remove(start.size()-1);//注意这里是start.remove(start.size()-1),不能是start.remove(0)end.add(val);}
}
注:
- 在move方法中,如果传三个参数,第一个参数代表起始柱子,第二个参数代表辅助柱子,第三个参数代表目标柱子;如果传两个参数,第一个参数代表起始柱子,第二个参数代表目标柱子
2. 合并两个有序链表
题目链接:21. 合并两个有序链表
这道题在链表那一节已经写过了,现在如果用递归去实现,该怎么写呢?
首先,解决一个递归问题的思路就是找出重复的子问题,据此可以设计出函数头;然后,只关心某一个子问题在做什么->函数体的设计;最后,递归的出口
- 比较list1的val值和list2的val值,较小值对应的指针往前走,较大值对应的指针保持不动
- 较小指针的next = 合并两个指针后面的子有序链表,重复1操作,直至有指针走完整个链表
实现代码:
class Solution {public ListNode mergeTwoLists(ListNode list1, ListNode list2) {if(list1 == null) {return list2;}if(list2 == null) {return list1;}int val1 = list1.val;int val2 = list2.val;if(val1 < val2) {list1.next = mergeTwoLists(list1.next, list2);return list1;}else {list2.next = mergeTwoLists(list1, list2.next);return list2;}}
}
3. 反转链表
题目链接:206. 反转链表
这道题在前面也同样做过,现在用递归来实现,下面用两个不同的视角来看待,本质上,两者都是一样的,只是第二种视角是第一种视角的展开
- 宏观视角
先当前节点后面的节点逆置,再把当前节点连接到逆置后的链表后面
- 把链表看作一个单分支的树
因此,想要逆置这个链表,可以采用后续遍历的思想
实现代码:
class Solution {public ListNode reverseList(ListNode head) {if(head == null || head.next == null) return head;ListNode newHead = reverseList(head.next);//newHead标记逆置后的链表的头节点head.next.next = head;head.next = null;return newHead;}
}
4. 两两交换链表中的节点
题目链接:24. 两两交换链表中的节点
实现代码:
class Solution {public ListNode swapPairs(ListNode head) {if(head == null || head.next == null) {return head;}ListNode newHead = head.next;head.next = swapPairs(newHead.next);newHead.next = head;return newHead;}
}
5. Pow(x,n)-快速幂
题目链接:50. Pow(x, n)
这题如果用暴力求解,时间成本会很高。换个角度,当我们求解xn时,我们可以先求出x n/2,再去求解x n;当我们需要求解x n/2时,我们可以先求出x n/4的值,再去求解x n/2…
算法思路:
- 根据子问题来设计出函数头:函数头需要两个参数,底数和指数,返回值为double类型
- 只关心某一个子问题做了什么,设计函数体:先求出x n/2等于多少,然后再根据n的奇偶性,得出x n等于多少
- 递归的出口:当n等于0时,返回1
实现代码:
class Solution {public double myPow(double x, int n) {//如果n小于0,则结果等于当指数为-n时结果的倒数return n >= 0 ? pow(x, n) : 1.0 / pow(x, -n);}private double pow(double x, long n) {if(n == 0) return 1.0;double tmp = pow(x, n / 2);//如果n是偶数,x ^ n 等于 x ^ n/2 * x ^ n/2 ;//如果n是奇数,x x ^ n 等于 x ^ n/2 * x ^ n/2 * nreturn n % 2 == 0 ? tmp * tmp : tmp * tmp * x;}
}