题目描述
我们要求找出具有下列性质数的个数(包含输入的自然数n):
先输入一个自然数n(n≤1000),然后对此自然数按照如下方法进行处理:
-
不作任何处理;
-
在它的左边加上一个自然数,但该自然数不能超过原数的一半;
-
加上数后,继续按此规则进行处理,直到不能再加自然数为止.
输入格式
自然数n,且n≤1000
输出格式
1个整数,表示具有该性质数的个数
输入输出样例
输入
6
输出
6
解释:满足的数分别为6,16,26,126,36,136
1.首次尝试
解法一 递归
显然,最简单直接的做法就是递归(将一个大问题规约成更小规模的子问题,通过子问题的求解完成原问题)
假设:
当输入为4时,满足的其性质的数有——4本身,加上其规模1/2的子问题解的个数,即 4, 24, 124
根据我们的分析,算法的也就不难写出来了,递归代码如下
int fun(int n) { //版本一 递归
int cnt = 0;
for(int i=1; i<=n/2; i++) {
cnt += fun(i);
}
return cnt+1;
}
2.超时
递归的方法虽然简单直观,但是提交没过——超时了,递归深度太深导致爆栈了,这时候我们就需要思考能不能改进?能不能进一步优化?
启发
还记得最初在学习递归的时候,一个经典的案例就是斐波那契数列:f(n)=f(n−1)+f(n−2),n≥2
我们发现,当递归调用的时候,其中某一个子树的值可能会被重复计算很多次,很费时间:
例如图中f(2)的值就被重复计算了三次,那么我们能不能让它只计算一次呢?
动态规划(DP)思想
对于斐波那契数列的一种优化方法就是利用数组暂存中间值,这样就避免了重复计算
那么什么又是动态规划?
遵循动态规划算法是将问题递归地分解成更简单的重叠子问题,通过子问题的解决来解决原问题
动态规划目前已广泛应用于经济学、计算机视觉、语音识别、人工智能、计算机图形学和生物信息学
在斐波那契数列的例子中,多次计算的f(2)的值,这样就是一个重叠的子问题,我们应避免重复计算带来的时间开销,于是引入了数组——保存每一次计算的值(这样一个过程称为记忆, 对于提高递归算法效率是很重要的技术)
3.优化
我们有了思路,下面的关注点就在于寻找该问题的一个递推关系式
我们先列出来一部分:(其中f(n)表示自然数n所具有上述三种性质的个数)
f(1)=1 | |
---|---|
f(2)=2 | f(3)=2 |
f(4)=4 | f(5)=4 |
f(6)=6 | f(7)=6 |
f(8)=10 | f(9)=10 |
… |
我们可以看出:
-
当n为奇数时(n>1),f(n)=f(n−1)
-
当n为偶数时,分析却复杂一些,我们不妨画出其递归树结构:
以f(4)为例,观察其结构,我们发现右框部分就是f(4/2)的个数,那么左边是什么呢?
仔细思考左框可以看出,左部分的个数相当于少了一个f(4/2)子树的个数,这不正好等于f(3)吗?
综上,我们可以得出递推式:
- f(1)=1
-
n>1时
- n为奇数,f(n)=f(n−1)
- n为偶数,f(n)=f(n−1)+f(n/2)
代码
long fun(int n){
long a[n+1];
a[0] = 1;
for(int i=1; i<n+1; i++){
if(i%2 != 0) //奇数
a[i] = a[i-1];
else
a[i] = a[i-1] + a[i/2];
}
return a[n];
}
4.小结
学习算法的道路漫长且艰巨,唯有不断打磨自己不断思考,挖掘其背后的本质,才能取得进步