由于动态规划解决的问题多数有重叠子问题这个特点,为减少重复计算,对每个子问题只解一次,将其不同阶段的不同状态保存在一个二维数组中
斐波那契数列
1,1,2,3,5,8,13,21,34……这样的数列
递归算法:
//求第n个数的值
//递归算法
public static int fib(int n) {
if(n==1||n==2) {
return 1;
}else {
return fib(n-1)+fib(n-2);
}
}
非递归算法:
递归算法在计算每一个n处的值时,都要向前递归,这时如果能将前面的值都保存起来就会更方便
非递归算法就是用一个数组将每一个数字保存起来,这样就不用在每次计算的时候,一个一个往回溯源,只要取这个数组中的值即可,避免了大量的重复计算
//非递归算法
public static int fib2(int n) {
//因为F数组是从F[1]开始存的 所以这里要多存一个数 用n+1!!!
int F[]=new int[n+1];
if(n<0) {
return -1;
}
F[1]=1;
F[2]=1;
//这里注意是i<=n不是i<n!!!
for(int i=3;i<=n;i++) {
F[i]=F[i-1]+F[i-2];
}
return F[n];
}
题目描述
大家都知道斐波那契数列,现在要求输入一个整数n,请你输出斐波那契数列的第n项(从0开始,第0项为0,第1项是1)。
n<=39
public class Solution {
public int Fibonacci(int n) {
//注意这里是n+2!!!为什么我还没想到 不应该是n+1吗
int[] F=new int[n+2];
F[0]=0;
F[1]=1;
for(int i=2;i<=n;i++){
F[i]=F[i-1]+F[i-2];
}
return F[n];
}
}
求不连续数字相加最大和-求最优解
比如求数组1,2,4,1,7,8,3中不连续的数字最大的和
分阶段,对第n个数字进行选还是不选
则有
这里的OPT(i)的公式就是我们的递归模型,代码根据这个来写
这时有两种情况:
OPT(0)=arr[0],OPT(1)=max{arr[0],arr[1]}
递归算法:
public static int max(int a,int b) {
return a>b?a:b;
}
//求不连续的数字最大和
//递归算法
public static int opt(int[] arr,int i) {
if(i==0) {
return arr[0];
}else if(i==1) {
return max(arr[0],arr[1]);
}else {
int a=arr[i]+opt(arr, i-2);
int b=opt(arr, i-1);
return max(a, b);
}
}
int arr[]= {1,2,4,1,7,8,3};
System.out.println(opt2(arr,arr.length-1));//15
非递归算法:
//非递归算法
public static int opt2(int[] arr,int i) {
int optarr[]=new int[i+1];
optarr[0]=arr[0];
optarr[1]=max(arr[0], arr[1]);
for(int j=2;j<optarr.length;j++) {
int a=optarr[j-2]+arr[j];
int b=optarr[j-1];
optarr[j]=max(a, b);
}
return optarr[i];
}
int arr[]= {1,2,4,1,7,8,3};
System.out.println(opt2(arr,arr.length-1));//15
在这里是用一个数组来存储,当arr数组中有0个数,有1个数,有2个数,有3个数……时,其最优值。例如:
数组arr[]= {1,2,4,1,7,8,3}
optarr[0]=1
optarr[1]=2
optarr[2]=max{optarr[0]+arr[2],optarr[1]}=5
optarr[3]=max{optarr[1]+arr[3],optarr[2]}=5
optarr[4]=max{optarr[2]+arr[4],optarr[3]}=12
optarr[5]=max{optarr[3]+arr[5],optarr[4]}=13
optarr[6]=max{optarr[4]+arr[6],optarr[5]}=15
在一个数组中选择若干个数字相加组合等于指定的数字,若可以组合则返回true,若不可以则返回false
比如求数组[3,34,4,12,5,2]中是否有一个组合使其相加等于9,如果有则返回true,没有则返回false
递归模型为:
这里有三种情况:
当s==0时,例如subset(arr[2],0),说明已经有组合满足条件,返回true
当遇到subset(arr[0],s)时,这时候只剩下一个数字进行比较。如果arr[0]等于s,则可以组合,如果arr[0]不等于s,则不可以组合。return arr[0]=s
当arr[i]>s时,如果选arr[i]的话,此时在i的前面永远不能拼凑出值为s的组合,所以这时只考虑不选的情况,即return subset(arr,i-1,s)
递归算法:
//求数组中是否能拼凑出值s
//递归算法
public static boolean subset(int[] arr,int i,int s) {
if(s==0) {
return true;
}else if(i==0) {
return arr[i]==s;
}else if(arr[i]>s) {
return subset(arr, i-1, s);
}else {
boolean a=subset(arr, i-1, s-arr[i]);
boolean b=subset(arr, i-1, s);
//这里只要a和b有一个满足即可
return a||b;
}
}
int arr[]= {3,34,4,12,5,2};
System.out.println(subset(arr,5,9));//true
System.out.println(subset(arr,5,13));//false
非递归算法:
这里是用一个二维数组来存储过程中的值,最后一行最后一列的值就是最后的返回值,即subset(arr[i],9)
//非递归算法
public static boolean subset2(int[] arr,int S) {
//先初始化一个二维数组,注意列数是s+1,因为是从0开始的
Boolean[][] subset=new Boolean[arr.length][S+1];
//初始化数组中的值
//当s=0时,都为true
for(int i=0;i<arr.length;i++) {
subset[i][0]=true;
}
//当i=0时,若arr[0]=s则为true,否则为false
for(int j=0;j<=S;j++) {
subset[0][j]=false;
}
subset[0][arr[0]]=true;
//其他情况
for(int i=1;i<arr.length;i++) {
for(int s=1;s<=S;s++) {
//当arr[i]大于s则不选arr[i]
if(arr[i]>s) {
subset[i][s]=subset[i-1][s];
}else {
boolean a=subset[i-1][s-arr[i]];
boolean b=subset[i-1][s];
subset[i][s]=a||b;
}
}
}
return subset[arr.length-1][S];
}
剪绳子
给你一根长度为n的绳子,请把绳子剪成整数长的m段(m、n都是整数,n>1并且m>1,m<=n),每段绳子的长度记为k[1],…,k[m]。请问k[1]x…xk[m]可能的最大乘积是多少?例如,当绳子的长度是8时,我们把它剪成长度分别为2、3、3的三段,此时得到的最大乘积是18。
动态规划
大概思路是,先初始化,当绳长为2时,最大值为1;将绳子分为两部分,j和i-j。(j从1开始++)有两种情况,i和i-j都不再分割,则dp[i]=j*(i-j);i-j继续分割,则dp[i]=j*dp[i-j]。最后的值要取这之间的最大值
public class Solution {
public int cutRope(int target) {
if(target<=1){
return -1;
}
int[] dp=new int[target+1];
dp[2]=1;
for(int i=3;i<=target;i++){
for(int j=1;j<i;j++){
//状态转移方程
//还没想明白为什么还要比较dp[i]
//应该是存在一种可能,绳子原来的长度比剪完几段相乘要大
dp[i] = Math.max(Math.max(j*(i-j),j*dp[i-j]),dp[i]);
}
}
return dp[target];
}
}
数位dp
数位dp一般用来统计一个区间内满足一些条件数的个数。
本质上是优化正常数数即暴力枚举的过程。其实就是dfs+记忆化数组
首先要有一个数位处理函数,将输入的数进行分解
然后就是一个dfs函数,来执行数位dp