分治法
将问题分解,通过求解局部性的小问题来解开原本的问题。这种技巧称为分治法。
递归
- 实现分治法需要使用递归。
- 在使用递归时我们需要设计递归函数,递归函数指的是自己调用自己的函数,是设计算法时候的一种技巧。
- 在我看来,递归最重要的无非是寻找大问题与小问题之间的关系和最终的递归终止条件。
- 在笔记(四)中,二分搜索曾经用到了递归的方法。
- 这里,先以最为经典的求n的阶乘作为例子来分析一下。
- 固然,这个问题可以用循环来实现
int factorial(int n){
if (n == 0 || n == 1) return 1;
else{
int ans = 1;
//循环
for (int i = 1; i <= n; i++){
ans *= i;
}
}
return ans;
}
- 但是当我们换一种思路,通过分治法的思想来考虑问题。首先,很容易能够得出n!和(n-1)!之间的关系,即n! = (n-1)! *n,如此我们就得到了一个比原来的问题小的局部问题:求(n-1)! 。对于(n-1)!又可以分为更小的与n-2相关的局部问题,就这样一直下去,直到终点。关于终止条件,也很容易想出当n是1或者n是0的时候停止,并返回1。代码如下
int factorial(int n){
if (n == 0 || n == 1) return 1; //终止条件
else return n * factorial(n-1); //递归调用
}
- 对于分治法来说,一旦想出了如何把大问题按照一种规则不断分解成局部问题,在程序的实现上可能会比其他的方法要简单一些,以下用leetcode的几道题来具体分析一下。
面试题 08.06. 汉诺塔问题
在经典汉诺塔问题中,有 3 根柱子及 N
个不同大小的穿孔圆盘,盘子可以滑入任意一根柱子。一开始,所有盘子自上而下按升序依次套在第一根柱子上(即每一个盘子只能放在更大的盘子上面)。移动圆盘时受到以下限制:
(1) 每次只能移动一个盘子;
(2) 盘子只能从柱子顶端滑出移到下一根柱子;
(3) 盘子只能叠在比它大的盘子上。请编写程序,用栈将所有盘子从第一根柱子移到最后一根柱子。
你需要原地修改栈。
示例1:
输入:A = [2, 1, 0], B = [], C = [] 输出:C = [2, 1, 0]
示例2:
输入:A = [1, 0], B = [], C = [] 输出:C = [1, 0]
提示:
A中盘子的数量不大于14个
- 汉诺塔可以说是最经典的可以利用分治法求解的问题之一。
- 我们要达到的目的就是把A的全部挪到C,但是只能操作最顶端的一块,并且大的盘子不能放在小的盘子上。
- 以下使用leetcode题解中的图片(by Pumpkin🎃)
- 对于复杂问题,我们可以先从简单的入手。
- 对于1块盘子来说,很简单,直接从A挪到C。
- 而对于两块盘子,我们就需要先将小盘子A放到B,再将比它大一点的盘子挪到C,最后再将它从B挪到C。
- 对于n块来说,我们通过通过分治法的思路来求解问题:
1.首先将n-1个盘子移到B
2.将最大的盘子从A移到C
3.把B上面的n-1个盘子移到C
- 可以看到以上这三步中,1和3都是可以通过递归求解的,而他们又都是相同类型的移动问题,所以我们只需要一个递归函数就可以解决
//首先我们来设计这个函数
void move(vector<int> &From, vector<int> &To, vector<int> &Swap, int amount){
//如果只有一个,就直接移到要去的地方
if (amount == 1) {
To.push_back(From.back());
From.pop_back();
return;
}
else{
//将上面的amount-1个移到空盘子上
move(From, Swap, To, amount-1);
//将最底下的一个移到要去的地方
To.push_back(From.back());
From.pop_back();
//再将刚刚用来作为暂时交换盘子上amount-1个盘子移到要去的地方
move(Swap, To, From, amount-1);
}
}
- 主体函数部分就是正确调用一遍move函数
class Solution {
public:
void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {
move(A, C, B, A.size());
}
void move(vector<int> &From, vector<int> &To, vector<int> &Swap, int amount){
//如果只有一个,就直接移到要去的地方
if (amount == 1) {
To.push_back(From.back());
From.pop_back();
return;
}
else{
//将上面的amount-1个移到空盘子上
move(From, Swap, To, amount-1);
//将最底下的一个移到要去的地方
To.push_back(From.back());
From.pop_back();
//再将刚刚用来作为暂时交换盘子上amount-1个盘子移到要去的地方
move(Swap, To, From, amount-1);
}
}
};