1、斐波那契数列
题目:
写一个函数,输入 n ,求斐波那契(Fibonacci)数列的第 n 项。斐波那契数列的定义如下:
F(0) = 0, F(1) = 1
F(N) = F(N - 1) + F(N - 2), 其中 N > 1.
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
方法1:递归解法
原理: 把 f(n) 问题的计算拆分成 f(n−1) 和 f(n−2)两个子问题的计算,并递归,以 f(0) 和 f(1)为终止条件。
缺点: 大量重复的递归计算,例如 f(n) 和 f(n−1)两者向下递归需要 各自计算 f(n−2)的值。
public class Solution {
public int Fibonacci(int n) {
if(n==0) return 0;
if(n==1) return 1;
return Fibonacci(n-1)+Fibonacci(n-2);
}
}
方法2:迭代法
public class Solution {
public int Fibonacci(int n) {
if(n==0) return 0;
if(n==1) return 1;
int fn1 = 0;
int fn2 = 1;
int fn = 0;
for(int i=2;i<=n;i++){
fn = fn1+fn2;
fn1 = fn2;
fn2 = fn;
}
return fn;
}
}
方法3:动态规划
状态定义: 设 dp为一维数组,其中 dp[i]的值代表 斐波那契数列第 i个数字 。
转移方程: dp[i]=dp[i-1]+dp[i−2]
初始状态: dp[0]=0, dp[1]=1 ,即初始化前两个数字;
返回值: dp[n] ,即斐波那契数列的第 n 个数字。
public class Solution {
public int Fibonacci(int n) {
if(n==0) return 0;
if(n==1) return 1;
int[] dp = new int[n+1];
dp[0] = 0;
dp[1] = 1;
for(int i=2;i<=n;i++){
dp[i] = dp[i-1]+dp[i-2];
}
return dp[n];
}
}
2、跳台阶
题目: 一只青蛙一次可以跳上1级台阶,也可以跳上2级。求该青蛙跳上一个n级的台阶总共有多少种跳法(先后次序不同算不同的结果)。
思路分析: 多少种可能性的题目一般都有递推性质 ,即 f(n)和 f(n−1)…f(1) 之间是有联系的。
设跳上 n级台阶有 f(n)种跳法。在所有跳法中,青蛙的最后一步只有两种情况: 跳上1 级或 2 级台阶。
当为 1 级台阶: 剩 n−1个台阶,此情况共有 f(n−1)种跳法;
当为 2 级台阶: 剩 n−2 个台阶,此情况共有 f(n−2)种跳法。
f(n)为以上两种情况之和,即f(n)=f(n−1)+f(n−2) ,以上递推性质为斐波那契数列。本题可转化为 求斐波那契数列第 n项的值
1、迭代法:
public class Solution {
public int JumpFloor(int target) {
if(target==1) return 1;
if(target==2) return 2;
int fn1 = 1;
int fn2 = 2;
int fn = 0;
for(int i=3;i<=target;i++){
fn = fn1+fn2;
fn1 = fn2;
fn2 = fn;
}
return fn;
}
}
2、动态规划算法:
状态转移:dp为一维数组,dp[i]代表第i个台阶的跳法
状态转移方程:dp[i] = dp[i-1]+dp[i-2]
初始状态:dp[1] = 1,dp[2]=2
返回结果:dp[n],即第n个台阶的跳法
代码:
public class Solution {
public int JumpFloor(int target) {
if(target==1) return 1;
if(target==2) return 2;
int dp[] = new int[target+1];
dp[1] = 1;
dp[2] = 2;
for(int i=3;i<=target;i++){
dp[i] = dp[i-1]+dp[i-2];
}
return dp[target];
}
}
3、变态跳台阶
题目:一只青蛙一次可以跳上1级台阶,也可以跳上2级……它也可以跳上n级。求该青蛙跳上一个n级的台阶总共有多少种跳法。
动态规划解法: 如何分析这个问题,确定转移方程:
状态转移:dp为一维数组,dp[i]代表第i个台阶的跳法
状态转移方程:dp[i] = sum[dp[j]] (1<=j<=i)
初始状态:dp[1] = 1
返回结果:dp[n],即第n个台阶的跳法
代码:
public class Solution {
public int JumpFloorII(int target) {
int[] dp = new int[target+1];
dp[1]=1;
for(int i=2;i<=target;i++){
for (int j=1;j<i;j++){
dp[i] += dp[j];
}
dp[i]+=1;
}
return dp[target];
}
}
4、矩形覆盖
题目:我们可以用2*1
的小矩形横着或者竖着去覆盖更大的矩形。请问用n个2*1
的小矩形无重叠地覆盖一个2*n
的大矩形,总共有多少种方法?
比如n=3时,2*3的矩形块有3种覆盖方法:
思路:用n个2*1
的小矩形无重叠地覆盖一个2*n
的大矩形,就是说高度只能为2
一维数组,dp[i]代表第2*i个大矩形有多少中装法
状态转移方程:dp[i] = dp[i-1]+dp[i-2]
初始状态:dp[1] = 1,dp[2]=2
返回结果:dp[n],2*n的大矩形有多少种装法
代码:
public class Solution {
public int RectCover(int target) {
if(target<2){
return target;
}
int[] dp = new int[target+1];
dp[1] = 1;
dp[2] = 2;
for(int i=3;i<=target;i++){
dp[i] = dp[i-1]+dp[i-2];
}
return dp[target];
}
}
对于动态规划的分析,可以考虑从前分析确定初始条件,从后分析确定前面一步和当前步有什么关系。
5、丑数-1
题目:编写一个程序判断给定的数是否为丑数。丑数就是只包含质因数 2, 3, 5
的正整数。
public boolean isUgly(int num) {
if(num == 0){
return false;
}
while (num != 1){
if(num % 2 == 0){
num /= 2;
continue;
}
if(num % 3 == 0){
num /= 3;
continue;
}
if(num % 5 == 0){
num /= 5;
continue;
}
return false;
}
return true;
}
6、丑数-2
题目:把只包含质因子2、3和5的数称作丑数(Ugly Number)。例如6、8都是丑数,但14不是,因为它包含质因子7。 习惯上我们把1当做是第一个丑数。求按从小到大的顺序的第N个丑数。
思路:丑数=丑数*丑数因子(2、3、5)
,丑数 xn+1 只可能是以下三种情况其中之一(索引 a,b,c为未知数):
package _12_丑数;
public class Solution {
public int GetUglyNumber_Solution(int index) {
if(index<=0){
return 0;
}
//定义三个索引
int index1 = 0;
int index2 = 0;
int index3 = 0;
int[] dp = new int[index];
dp[0] = 1;
for(int i=1;i<index;i++){
//当前丑数
int num = dp[i-1];
//寻找第一个乘以2后大于等于当前丑数的丑数
while (num>=dp[index1]*2){
index1++;
}
//寻找第一个乘以3后大于等于当前丑数的丑数
while(num>=dp[index2]*3){
index2++;
}
//寻找第一个乘以5后大于等于当前丑数的丑数
while (num>=dp[index3]*5){
index3++;
}
//当退出循环后,我们就找到了想找的丑数,比较哪个丑数最小放入数组,作为下一个丑数
dp[i] = Math.min(Math.min(dp[index1]*2,dp[index2]*3),dp[index3]*5);
}
return dp[index-1];
}
}
对上述代码的优化:
public class Solution {
public int GetUglyNumber_Solution(int index) {
if(index<=0){
return 0;
}
//定义三个索引
int index1 = 0;
int index2 = 0;
int index3 = 0;
int[] dp = new int[index];
dp[0] = 1;
for(int i=1;i<index;i++ ){
//从三个丑数中找到最小的一个存入dp[i]
dp[i] = Math.min(Math.min(dp[index1]*2,dp[index2]*3),dp[index3]*5);
//更新索引
if(dp[i]==dp[index1]*2){
index1++;
}
if(dp[i]==dp[index2]*3){
index2++;
}
if(dp[i]==dp[index3]*5){
index3++;
}
}
return dp[index-1];
}
}