第1题:Fibonacci(斐波那契数列):
大家都知道斐波那契数列吧,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0)。
首先用递归的方法:
package com.revision.Algorithm;
import java.util.Scanner;
//递归时间复杂度为O(2^n)
//
public class Fibonacci {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); //用于接收数据
System.out.println("第" + n + "项的斐波那契数是" + Solution(n));
}
private static int Solution(int n) {
if(n == 0){
return 0;
}
if(n == 1 || n == 2){
return 1;
}
return Solution(n - 2) + Solution(n - 1);
}
}
递归的方法时间复杂度为O(2^n),随着n的增大呈现指数增长,效率低下
当输入比较大时,可能导致栈溢出
在递归过程中有大量的重复计算
我们此时应该采用动态规划的方式进行思考:
状态:F(n)
状态递推:F(n)=F(n-1)+F(n-2)
初始值:F(1)=F(2)=1
返回结果:F(N)
package com.revision.Algorithm;
import java.util.Scanner;
//递归时间复杂度为O(2^n)
//
public class Fibonacci {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); //用于接收数据
/* System.out.println("第" + n + "项的斐波那契数是" + Solution(n));*/
System.out.println("第" + n + "项的斐波那契数是" + Solution1(n));
}
/*private static int Solution(int n) {
if(n <= 0){
return 0;
}
if(n == 1 || n == 2){
return 1;
}
return Solution(n - 2) + Solution(n - 1);
}*/
private static int Solution1(int n) {
//初始值
if(n <= 0){
return 0;
}
if(n == 1 || n == 2){
return 1;
}
//申请一个数组 用来保存子问题的解
int[] array = new int[n + 1];
array[0] = 0;
array[1] = 1;
array[2] = 1;
for(int i = 3;i <= n;i++){
//F(n) = F(n - 1) + F(n - 2)
array[i] = array[i-1] + array[i-2];
}
return array[n];
}
}
上述解法的空间复杂度为O(n)
其实F(n)只与它相邻的前两项有关,所以没有必要保存所有子问题的解
只需要保存两个子问题的解就可以
下面方法的空间复杂度将为O(1)
package com.revision.Algorithm;
import java.util.Scanner;
//递归时间复杂度为O(2^n)
//
public class Fibonacci {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt(); //用于接收数据
/* System.out.println("第" + n + "项的斐波那契数是" + Solution(n));
System.out.println("第" + n + "项的斐波那契数是" + Solution1(n));*/
System.out.println("第" + n + "项的斐波那契数是" + Solution2(n));
}
/*private static int Solution(int n) {
if(n <= 0){
return 0;
}
if(n == 1 || n == 2){
return 1;
}
return Solution(n - 2) + Solution(n - 1);
}
private static int Solution1(int n) {
//初始值
if(n <= 0){
return 0;
}
if(n == 1 || n == 2){
return 1;
}
//申请一个数组 用来保存子问题的解
int[] array = new int[n + 1];
array[0] = 0;
array[1] = 1;
array[2] = 1;
for(int i = 3;i <= n;i++){
array[i] = array[i-1] + array[i-2];
}
return array[n];
}*/
private static int Solution2(int n) {
//初始值
if(n <= 0){
return 0;
}
if(n == 1 || n == 2){
return 1;
}
//由于结果只和它的前两个值有关 我们每次计算完就对两个临时变量进行重新赋值 比刚才的方法
//更加节省空间 时间复杂度因为没有了最后的数组查找 变成了O(1)
int fn1 = 0;
int fn2 = 1;
int res = 0;
for(int i = 2;i <= n;i++){
res = fn1 + fn2;
fn1 = fn2;
fn2 = res;
}
return res;
}
}
ok这一题相信已经有了最优解了吧。
那么来看下一题:
第2题:青蛙跳台阶(Climbing Stairs):
一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
我们先利用动态规划分析一下这个问题:
状态:
子状态:跳上1级,2级,3级,…,n级台阶的跳法数
f(n):还剩n个台阶的跳法数
状态递推:
n级台阶,第一步有n种跳法:跳1级、跳2级、到跳n级
跳1级,剩下n-1级,则剩下跳法是f(n-1)
跳2级,剩下n-2级,则剩下跳法是f(n-2)
f(n) = f(n-1)+f(n-2)+…+f(n-n)
f(n) = f(n-1)+f(n-2)+…+f(0)
f(n-1) = f(n-2)+…+f(0)
f(n) = 2*f(n-1)
初始值:
f(1) = 1
f(2) = 2f(1) = 2
f(3) = 2f(2) = 4
f(4) = 2*f(3) = 8
所以它是一个等比数列
f(n) = 2^(n-1)
返回结果:f(N)
代码实现为:
package com.revision.Algorithm;
import java.util.Scanner;
//青蛙一次可以跳1级台阶,也可以2级。。。。。。也可以n级,求跳上n级台阶有多少种方法
public class JumpFlog {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
System.out.println("跳"+n+"阶台阶的方法数有" + Solution(n)+"种!");
}
private static int Solution(int n) {
if(n <= 0){
return 0;
}
int res = 1;
//利用循环实现F(N) = 2^(N-1)
for(int i = 1;i <= n-1;i++){
res *= 2;
}
return res;
}
}
再换一个思路:利用排列的思想去做:
每个台阶看成一个位置,除过最后一个位置,其它位置都有两种可能性,也就是走还是不走
所以总的排列数为2^(n-1)*1
也就是2^(n-1)
代码其实和上面是一样的,但是这样想起来显然更加简便!
再思考一下,这样做的时间复杂度应该是多少?是O(n)对吧
有没有可能变为O(1)呢?
请看代码:
package com.revision.Algorithm;
import java.util.Scanner;
//青蛙一次可以跳1级台阶,也可以2级。。。。。。也可以n级,求跳上n级台阶有多少种方法
public class JumpFlog {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
System.out.println("跳"+n+"阶台阶的方法数有" + Solution(n)+"种!");
System.out.println("跳"+n+"阶台阶的方法数有" + Solution1(n)+"种!");
}
private static int Solution(int n) {
if(n <= 0){
return 0;
}
int res = 1;
//利用循环实现F(N) = 2^(N-1)
for(int i = 1;i <= n-1;i++){
res *= 2;
}
return res;
}
//可以利用左移一位的思想 左移一位相当于乘一个2
private static int Solution1(int n) {
if(n <= 0){
return 0;
}
//左移n-1就是乘2的(n-1)次方喽。。。
int res = 1;
return 1 << (n - 1);
}
}
直接利用左移,一下子就完成了上面的循环的操作 时间复杂度就可以来到N(1),是不是很方便呢?
此题看似复杂,通过抽象和归纳,可以找出问题的内在规律
定义问题的状态,以及状态间的递推关系,找到问题的答案
现在让它变成一个正常的青蛙,限制它一次只能跳1阶或者2阶,现在该如何解答?
矩形覆盖:
我们可以用21的小矩形横着或者竖着去覆盖更大的矩形。
请问用n个21的小矩形无重叠地覆盖一个2*n的大矩形,总共有多少种方法?
都可以用斐波那契数列去做的
先看看这个新的青蛙跳台阶题:
我们用归纳法先观察:
哦豁 这 1 2 3 5 8 …不就是斐波那契数列吗 换汤不换药嘛
那仔细想想 这个矩形覆盖,是不是也是一样的啊?