目录
3.连续子数组最大和_牛客网 https://连续子数组最大和
一、动态规划
1.简单将dp问题理解为三个思路:
1)定义状态
2)编写状态转移方程
3)设置初始值
2.什么时候用动归
通过大量题型了解,找到一批动归题目,先练习三种思路
一般使用动归,必定使用数组(一维、二维)
3+1:三个过程+一个数组
二、题目
1.青蛙跳台阶问题
一只青蛙,一次可以跳一级台阶,也可以跳两级,那它到n级台阶有多少跳法1.定义状态
1)定义状态
f(n):青蛙跳上n级台阶的总跳法数
f(n-1):青蛙跳上n-1级台阶的总跳法数
f(n-2):青蛙跳上n-2级台阶的总跳法数
2)状态转移方程
f(n)=f(n-1)+f(n-2)
n级台阶时青蛙一定是从n-1或n-2跳上来的,没有其他跳法,所以它到n的跳法一定是n-1的跳法+n-2的跳法
3)初始值
f(0)=1:还未跳时,可以不要它,如果想要,就认为它是起始台阶,到0台阶有一种方法即不跳
f(1)=1:到第一级台阶就一种跳法
f(2)=2:到第二级台阶两种跳法(先到第一级再到第二级或直接到第二级)
public int jumpFloor(int target) {
if(target==0){return 1;}
if(target==1||target==2){
return target;//加上否则有越界访问问题,问题输入是输入1越界
}
// //f(n)=f(n-1)+f(n-2)
// int[] dp=new int[target+1];//下标+1,让第一个台阶从1开始,第二个台阶从2开始
// dp[0]=1;
// dp[1]=1;
// dp[2]=2;
// for(int i=2;i<=target;i++){
// dp[i]=dp[i-1]+dp[i-2];
// }
// return dp[target];
int first=1;
int second=1;
int third=1;
for (int i = 2; i <=target ; i++) {
third=first+second;
first=second;
second=third;
}
return third;
}
2.矩形覆盖_牛客网
我们可以用 2*1 的小矩形横着或者竖着去覆盖更大的矩形。请问用 n 个 2*1 的小矩形无重叠地覆盖一个 2*n 的大矩形,从同一个方向看总共有多少种不同的方法?
分析:
1)状态定义:f(n):用n个2*1的小矩形无重叠的覆盖一个2*n的大矩形所用的总方法数
2)状态递推:f(n)=f(n-1)【最后一个竖着放】+f(n-2)【最后两个横着放】
f(n-1):最后一个是竖着放时前面总共的放法数;f(n-2):最后两个是横着放时前面的总放法数
3)初始化:f(1)=1;f(2)=2;
public int rectCover(int target) {
//f(n):2*1->2*n:总方法数
if(target==0||target==1||target==2){
return target;
}
int[] dp=new int[target+1];//最后一个元素的下标是target,target是5的话就是第五次
dp[1]=1;
dp[2]=2;
for(int i=3;i<=target;i++){
dp[i]=dp[i-1]+dp[i-2];
}
int res=dp[target];
return res;
}
3.连续子数组最大和_牛客网 https://连续子数组最大和
分析:用到动态规划,状态方程式为:
max(dp[i])=getMax(max(dp[i-1]+arr[i],arr[i]),dp[i-1])//max(dp[i-1]+arr[i],arr[i])是当前dp[i]的值
2 | -3 | -2 | 7 | -5 | 1 | 2 | 2 |
dp[i]:以i结尾的数组元素的最大和,如dp[3]时,有两种求法:1)dp[i-1]+arr[i];2)arr[i],求法1得到结果2-3-2+7=4,求法2得到结果就是7,求法二得到结果较大,所以取较大值,dp[3]=7。
取到当前max(dp[i])后再循环向后遍历,拿到每个对应位置的max(dp[i]),同时将相对大的值赋值给结果result,最后输出的result就是连续子集的最大和了。
import java.util.*;
public class Num2{
public static int getMax(int a,int b){
return a>b?a:b;
}
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
int n=sc.nextInt();//数组元素个数
int[] array=new int[n];
for(int i=0;i<n;i++){
//给数组元素赋值
array[i]=sc.nextInt();
}
//将0位置索引值作为初始值,i从1开始
//当前子数组的和
int sum=array[0];
//连续子数组最大和——最终要输出的结果
int max=array[0];
for(int i=1;i<n;i++){
//获取arr[i]+sum(以i-1结尾的数组的最大和)与arr[i]的较大值
//初始以i-1结尾的数组的最大和就是array[0]
sum=getMax(sum+array[i],array[i]);
//判断此时的sum和max哪一个大(dp[i]和dp[i-1]比较)
if(sum>=max){
max=sum;
}
}
System.out.println(max);
}
}
一样的题型:给定整数序列求连续子串最大和_牛客网 https://www.nowcoder.com/questionTerminal/9d216205fbb44e538f725d9637239523
import java.util.*;
public class Main{
public static int getMax(int a,int b){
return a>b?a:b;
}
public static void main(String[] args){
Scanner sc=new Scanner(System.in);
String[] s=sc.nextLine().split(" ");
int[] array=new int[s.length];
for(int i=0;i<array.length;i++){
array[i]=Integer.parseInt(s[i]);
}
//此时拿到整型数组
//开始进行最大子数组求和
int sum=array[0];//sum计算直到当前i位置的所有元素的最大子数组元素和
int max=array[0];//max用来记录每次最大的sum和max的较大值,max记录最终结果
for(int i=1;i<array.length;i++){
//直到当前i位置最大子数组元素和
sum=getMax(sum+array[i],array[i]);
//比较max与sum大小
if(sum>=max){
//更新结果集
max=sum;
}
}
System.out.println(max);
}
}
4.斐波那契数列
斐波那契数列由 0 和 1 开始,之后的斐波那契数就是由之前的两数相加而得出。
答案需要取模 1e9+7(1000000007),如计算初始结果为:1000000008,请返回 1
int MOD=(int)le9+7,进行取模操作即可。
/**
* 求n的斐波那契数列
**/
public class Num10I_斐波那契数列 {
public int fib(int n) {
final int MOD = 1000000007;//对大数进行取模操作,让结果不至于溢出
if(n==0||n==1){
return n;
}
if(n==2){
return 1;
}
int first=1;
int second=1;
int third=1;
while (n>2){
third=first+second;
third%=MOD;
first=second;
second=third;
n--;
}
return third;
}
}
5.股票最大利润
1)
/**
* 假设把某股票的价格按照时间先后顺序存储在数组中,请问买卖该股票一次可能获得的最大利润是多少?
* 输入: [7,1,5,3,6,4]
* 输出: 5
* 解释: 在第2天(股票价格 = 1)的时候买入,在第5天(股票价格=6)的时候卖出,最大利润 = 6-1 = 5 。
* 注意利润不能是 7-1 = 6, 因为卖出价格需要大于买入价格
**/
/**
* max(price[j]-price[i]):最大差值。
* 我们找出数组中两数字之间最大差值即可获得最大利润,并且第二个数>第一个数
* 我们只要用一个变量记录一个历史最低价格 minprice,我们就可以假设自己的股票是在那天买的。
* 那么我们在第 i 天卖出股票能得到的利润就是 prices[i] - minprice
*/
public class Num63_股票最大利润 {
public int maxProfit(int[] prices) {
int minPrice=Integer.MAX_VALUE;//初始化为整型最大值
int maxProfit=0;//最大利润(利润=当前遍历价格-最低价格,最大利润取最大值即可)
for (int i = 0; i < prices.length; i++) {
if(prices[i]<minPrice){//将最小值放入minPrice中
minPrice=prices[i];
}
if(prices[i]-minPrice>maxProfit){//当前遍历价格-最低价格>差价,更新差价
maxProfit=prices[i]-minPrice;
}
}
return maxProfit;
}
}
2)动态规划求解
定义状态:dp[i]:以prices[i]为结尾的子数组的最大利润(即前i日的最大利润)
转移方程:前i日最大利润=前i-1日最大利润dp[i-1]和第i日最大利润中的较大值
=max(前(i−1)日最大利润,第i日价格−前i日最低价格)
dp[i]=max(dp[i−1],prices[i]−min(prices[0:i]))初始状态:dp[0]=0
/**
* 定义状态:dp[i]:以prices[i]为结尾的子数组的最大利润(即前i日的最大利润)
* 转移方程:前i日最大利润=前i-1日最大利润dp[i-1]和第i日最大利润中的较大值
* dp[i]=max(dp[i−1],prices[i]−min(prices[0:i]))
*/
public int maxProfit(int[] prices) {
int minPrice=Integer.MAX_VALUE;//购入的最小价格
int profit=0;//利润
for (int price : prices) {//price的值直接是数组中元素的值
minPrice=Math.min(minPrice,price);//最小购入价格
profit=Math.max(profit,price-minPrice);//右profit是上一循环中的差价,price-minPrice是当前购入值与最小值的差价,比较大者给profit
}
return profit;
}
6.礼物的最大值力扣
1.定义状态:设动态规划矩阵 dp(i,j)代表从棋盘的左上角开始,到达单元格(i,j) 时能拿到礼物的最大累计价值。
2.状态方程:grid(i,j)=max[f(i,j−1),f(i−1,j)]+grid(i,j)
/**
* 某单元格只可能从上边单元格或左边单元格到达。
* 设f(i, j)为从棋盘左上角走至单元格(i ,j)的礼物最大累计价值
* 易得到以下递推关系:f(i,j)f(i,j) 等于 f(i,j-1)f(i,j−1) 和 f(i-1,j)f(i−1,j) 中的较大值加上当前单元格礼物价值 grid(i,j)grid(i,j) 。
* f(i,j)=max[f(i,j−1),f(i−1,j)]+grid(i,j)
**/
/**
* 状态方程
* 当 i=0 且 j=0 时,为起始元素;最大价值就是当前单元格礼物价值;dp(i,j)=grid(i,j)
* 当 i=0 且 j!=0时,为矩阵第一行元素,只可从左边到达;dp(i,j)=grid(i,j)+dp(i-1,j);
* 当 i!=0且 j=0 时,为矩阵第一列元素,只可从上边到达;dp(i,j)=grid(i,j)+dp(i,j-1);
* 当 i !=0 且 j!=0 时,可从左边或上边到达dp(i,j)=grid(i,j)+max(dp(i,j-1),dp(i-1,j));
*/
public class Num47_礼物的最大价值 {
public int maxValue(int[][] grid) {
int m=grid.length;//行
int n= grid[0].length;//遍历列
for (int i = 0; i < m; i++) {
for (int j = 0; j < n; j++) {
if(i==0&&j==0){
//其实元素,最大价值就是当前格元素的礼物价值
continue;
} else if(i==0){
//一行
grid[i][j]=grid[i][j]+grid[i][j-1];
} else if(j==0){
//一列
grid[i][j]=grid[i][j]+grid[i-1][j];
} else {
grid[i][j]=grid[i][j]+Math.max(grid[i-1][j],grid[i][j-1]);
}
}
}
return grid[m-1][n-1];
}
}
7.力扣_数字打印成字符串;
1.如果是最后一个字符:整个数字的翻译结果=除去最后一位的部分的翻译结果(根据题目,0-9都可以翻译,所以最后一位一定可以翻译并且只有一种法子翻译)
2.如果恰好最后两个字符可以翻译,整个=除去最后2位部分的翻译结果,因为最后两位如果可译,这两位也只会对应一种翻译结果:
3.总结一二中的两种情况考虑,整个数字的翻译结果f(i)=f(i-1)位上的总翻译结果+f(i-2)上的总翻译结果:
4.dp
定义状态:dp[i]表示nums[0...i]前i位能翻译成字符串的种类数
状态转移方程:dp[i]=dp[i-1]+dp[i-2](若nums[i-1...i]可以翻译)
初始状态:dp[0]=1(0-9各自可以翻译成一种结果)
输出:dp[len-1]
/**
* 一串数字翻译成字符串多少种翻译方法
**/
public class Num46_把数字翻译成字符串 {
//dp[i]=dp[i-1]:因为一个数字一定可以翻译,所以dp[i]的值至少是dp[i-1]
//dp[i-2]不能组合时,dp[i]=dp[i-1],可以组合时,dp[i]=dp[i-1]+dp[i-2]
public int translateNum(int num) {
//数字转化为字符串,字符串转化为数组
String s=String.valueOf(num);
int length=s.length();
if(length<2){
//只有一位数或没有数字,返回1或0种方法
return length;
}
char[] charArray=s.toCharArray();
int[] dp=new int[length];
dp[0]=1;
for (int i = 1; i < length; i++) {
dp[i]=dp[i-1];
//判断当前位和前一位能否组成10-25的数(dp[i-2]是否存在)
int n=10*(charArray[i-1]-'0')+(charArray[i]-'0');
if(n>9&&n<26){
if(i<2){
dp[i]++;
}else{
dp[i]+=dp[i-2];
}
}
}
//循环完毕,dp数组最后一位即为最终统计值
return dp[length-1];
}
}
8.力扣_整数拆分
/**
* 给定一个正整数 n ,将其拆分为 k 个 正整数 的和( k >= 2 ),并使这些整数的乘积最大化。
* 返回 你可以获得的最大乘积 。
**/
/**
* dp[i] 表示将正整数i拆分成至少两个正整数的和之后,这些正整数的最大乘积
* 特别地,0不是正整数,1是最小的正整数,0和1都不能拆分,因此dp[0]=dp[1]=0
* 当i≥2时,假设对正整数i拆分出的第一个正整数是j(1≤j<i),则有以下两种方案:
* 将i拆分成j和i−j的和,且i−j不再拆分成多个正整数,此时的乘积是j×(i−j);
* 将i拆分成j和i−j的和,且i−j继续拆分成多个正整数,此时的乘积是j×dp[i−j]
*/
public class Num343_整数拆分 {
public int integerBreak(int n) {
int[] dp=new int[n+1];
for (int i = 2; i <= n; i++) {
int resMax=0;
for (int j = 0; j < i; j++) {
resMax=Math.max(resMax,Math.max(j*(i-j),j*dp[i-j]));
}
dp[i]=resMax;
}
return dp[n];
}
}