动态规划Dynamic Programming
1,递推(递归+记忆)
2.状态的定义:opt[n],dp[n],fib[n]
3,状态转移方程:opt[n]=best_of(opt[n-1],opt[n-2],…)
4.最优子结构
DP vs 回溯 vs 贪心
回溯(递归) ——重复计算
贪心——永远局部最优
DP——记录局部最优子结构/多种记录值
70.爬楼梯
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
注意:给定 n 是一个正整数。
示例1:
输入: 2
输出: 2
解释: 有两种方法可以爬到楼顶。
1. 1 阶 + 1 阶
2. 2 阶
示例2:
输入: 3
输出: 3
解释: 有三种方法可以爬到楼顶。
1. 1 阶 + 1 阶 + 1 阶
2. 1 阶 + 2 阶
3. 2 阶 + 1 阶
【实现代码】
package leetcode_50;
import java.util.Scanner;
public class Leetcode_70 {
public static void main(String[] args){
Scanner scanner=new Scanner(System.in);
int n=scanner.nextInt();
int result,result2;
result=climbstairs(n);
result2=climbstairs2(n);
System.out.println(result2);
System.out.print(result);
}
private static int climbstairs2(int n) {
if(n<=2) return n;
int one_step_before=2;
int two_step_before=1;
int all_ways=0;
for(int j=2;j<n;j++){
all_ways=one_step_before+two_step_before;
two_step_before=one_step_before;
one_step_before=all_ways;
}
return all_ways;
}
private static int climbstairs(int n) {
if(n==0||n==1||n==2){
return n;
}
int[] mem=new int[n];
mem[0]=1;
mem[1]=2;
for(int i=2;i<n;i++){
mem[i]=mem[i-1]+mem[i-2];
}
return mem[n-1];
}
}
120.三角形的最小路径和
给定一个三角形,找出自顶向下的最小路径和。每一步只能移动到下一行中相邻的结点上。
例如,给定三角形:
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
自顶向下的最小路径和为11(即,2 + 3 + 5 + 1 = 11)。
【分析】
1.定义状态:dp[i,j]:是指从底走到i,j路径和的最小值。
2.方程;dp[i,j]=min(dp[i+1,j],dp[i+1,j+1])+本身这点的数据
3.初始状态:dp[m-1,j]=triangle[m-1.j]
【实现代码】
import java.util.ArrayList;
class Solution {
public int minimumTotal(List<List<Integer>> triangle) {
int len=triangle.size();
if(len==0) return 0;
int[] dp=new int[triangle.size()];
for(int i=0;i<triangle.size();i++){
dp[i]=triangle.get(triangle.size()-1).get(i);
}
//从倒数第二层开始
for(int i=triangle.size()-2;i>=0;i--){
List<Integer> curlist=triangle.get(i);
for(int j=0;j<curlist.size();j++)
dp[j]=Math.min(curlist.get(j)+dp[j],curlist.get(j)+dp[j+1]);
}
return dp[0];
}
}
152.乘积最大子序列
给定一个整数数组 nums ,找出一个序列中乘积最大的连续子序列(该序列至少包含一个数)。
示例1:
输入: [2,3,-2,4]
输出: 6
解释: 子数组 [2,3] 有最大乘积 6。
示例2:
输入: [-2,0,-1]
输出: 0
解释: 结果不能为 2, 因为 [-2,-1] 不是子数组。
【思考】
1.状态:dp[i][2]
2.dp方程:
0:max
1:负max
(正的最大值)dp[i,0]= if a[i]>=0: dp[i-1,0]*a[i]
else :dp[i-1,1]*a[i]
(负的最大值)dp[i,1]= if a[i]>=0:dp[i-1,1]a[i]
else:dp[i-1,0]a[i]
以上的思想也就是利用动态规划,遍历数组计算当前最大值,不断更新。令imax为当前最大值,则当前最大值为Imax=max(imaxa[i],a[i])由于存在负数,导致最大的变最小,最小的变最大。因此还需要维护当前最小值imin,imin=min(imina[i],a[i])。当负数出现时,则imax与imin进行交换再进行下一步计算。
【实现代码】
package leetcode_50;
import java.util.Scanner;
import org.omg.PortableInterceptor.IORInterceptor;
public class leetcode_152 {
public static void main(String[] args){
int result;
Scanner sc=new Scanner(System.in);
String string=sc.nextLine();
String[] s=string.split(" ");
int[] a=new int[s.length];
for(int i=0;i<s.length;i++){
a[i]=Integer.parseInt(s[i]);
}
result=maxProduct(a);
System.out.println(result);
}
private static int maxProduct(int[] a) {
int max=Integer.MIN_VALUE,imax= 1,imin=1;
for(int i=0;i<a.length;i++){
if(a[i]<0){
int tmp=imax;
imax=imin;
imin=tmp;
}
imax=Math.max(imax*a[i], a[i]);
imin=Math.min(imin*a[i], a[i]);
max=Math.max(max, imax);
}
return max;
}
}
121.买卖股票最佳时机
【实现代码】
import numpy as np
def maxProfit(prices):
if not prices: return 0;
count=0
res=0
profit=[[0 for i in range(3)] for i in range(len(prices))]
# profit=np.array(profit)
print(profit)
profit[0][0],profit[0][1],profit[0][2]=0,-prices[0],0
for i in range(1,len(prices)):
# print(i)
profit[i][0]=profit[i-1][0]
# print(profit[i][0])
profit[i][1]=max(profit[i-1][1],profit[i-1][0]-prices[i])
profit[i][2] = profit[i - 1][1] + prices[i]
# print(profit[i][1])
# print(profit[i][2])
res=max(res,profit[i][0],profit[i][1],profit[i][2])
# print(res)
# if(res>0): count+=res
print(res)
if __name__ == '__main__':
line=input('请以空格为间隔连续输入一个数组')
# a=line[1:len(line)-1]
# a=a.split(" ")
# a=[int(i) for i in a]
a=[int(n) for n in line.split()]
prices=a
maxProfit(prices)
# print(result)
300.最长上升子序列
给定一个无序的整数数组,找到其中最长上升子序列的长度。
示例:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。
说明:
可能会有多种最长上升子序列的组合,你只需要输出对应的长度即可。
你算法的时间复杂度应该为 O(n2) 。
【思路】
1.状态:dp[i]从头->i元素:最长子序列的长度
max(dp[0][1][2]…[n-1])
2.状态转移方程:
for i:0->n-1
dp[i]=Max{dp[j]}+1
注意:j:0->i-1且a[j]<a[i]
【实现代码】
// An highlighted block
package leetcode_50;
import java.util.Arrays;
import java.util.Scanner;
public class Leetcode_300 {
public static void main(String[] args){
int result;
Scanner sc=new Scanner(System.in);
String string=sc.nextLine();
String[] s=string.split(" ");
int[] a=new int[s.length];
for(int i=0;i<s.length;i++){
a[i]=Integer.parseInt(s[i]);
}
//result=maxProfit(a);
result=lengthOfLIS(a);
System.out.println(result);
}
private static int lengthOfLIS(int[] nums) {
int[] dp=new int[nums.length+1];
int res=1;
Arrays.fill(dp, 1);
int count=0;
for(int i=0;i<nums.length;i++){
for(int j=0;j<i;j++){
if(nums[j]<nums[i]){
//System.out.println(dp[i]);
//System.out.println(i+" "+j);
dp[i]=Math.max(dp[i], dp[j]+1);
//dp[i]=dp[j]+1;
}
}
//整体的最大值
res=Math.max(res, dp[i]);
}
// for(int i=0;i<dp.length;i++){
// System.out.println(dp[i]);
// }
return res;
}
}
322.零钱兑换
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例2:
输入: coins = [2], amount = 3
输出: -1
说明:
你可以认为每种硬币的数量是无限的。
【思路】
可以理解为之前做过的**“爬楼梯”**问题,爬十一级台阶,一次爬一层,两层或五层。
动态规划:
1.状态:dp[i]:表示到第i级台阶的最少步数。
2.状态方程:
dp[i]=min{dp[i-coins[j]]}+1
【实现代码】
package leetcode_50;
import java.util.Arrays;
import java.util.Scanner;
public class Leetcode_322 {
public static void main(String[] args){
int result;
Scanner sc=new Scanner(System.in);
Scanner sc1=new Scanner(System.in);
int amount=sc1.nextInt();
String string=sc.nextLine();
String[] s=string.split(",");
int[] a=new int[s.length];
for(int i=0;i<s.length;i++){
a[i]=Integer.parseInt(s[i]);
}
result=coinChange(a,amount);
System.out.println(result);
}
private static int coinChange(int[] coins, int amount) {
//dp的大小
int[] dp=new int[amount+1];
Arrays.fill(dp, amount+1);
dp[0]=0;
//注意到amount
for(int i=0;i<=amount;i++){
for(int j=0;j<coins.length;j++){
if(coins[j]<=i){
dp[i]=Math.min(dp[i], dp[i-coins[j]]+1);
}
}
}
return dp[amount]>amount ? -1:dp[amount];
}
}
72.编辑距离
给定两个单词 word1 和 word2,计算出将 word1 转换成 word2 所使用的最少操作数 。
你可以对一个单词进行如下三种操作:
插入一个字符
删除一个字符
替换一个字符
示例1:
输入: word1 = "horse", word2 = "ros"
输出: 3
解释:
horse -> rorse (将 'h' 替换为 'r')
rorse -> rose (删除 'r')
rose -> ros (删除 'e')
示例2
输入: word1 = "intention", word2 = "execution"
输出: 5
解释:
intention -> inention (删除 't')
inention -> enention (将 'i' 替换为 'e')
enention -> exention (将 'n' 替换为 'x')
exention -> exection (将 'n' 替换为 'c')
exection -> execution (插入 'u')
【思路】
1.状态:dp[i][j]:word1匹配到word2的前j个字符最少步数
i:单词1的前i个字符
j:单词2的前j个字符
结果为dp[m][n]
2.状态转移方程:
dp[i,j]= if w1[i]=w2[j] dp[i-1][j-1]
else: //插入,删除,替换
min(dp[i-1,j],dp[i,j-1],dp[i-1,j-1])+1(进行了一次操作要加一)
【实现代码】
package leetcode_50;
import java.util.Arrays;
import java.util.Scanner;
public class Leetcode_72 {
public static void main(String[] args){
int result;
Scanner sc=new Scanner(System.in);
Scanner sc1=new Scanner(System.in);
String word1=sc.nextLine();
String word2=sc.nextLine();
result=minDistance(word1,word2);
System.out.println(result);
}
private static int minDistance(String word1, String word2) {
int m=word1.length();
int n=word2.length();
int[][] dp=new int[m+1][n+1];
// for(int i=0;i<m+1;i++)
// for(int j=0;j<n+1;j++){
// dp[i][j]=0;
// }
for(int i=0;i<m+1;i++){
dp[i][0]=i;
}
for(int j=0;j<n+1;j++){
dp[0][j]=j;
}
for(int i=1;i<m+1;i++)
for(int j=1;j<n+1;j++){
if(word1.charAt(i-1)==word2.charAt(j-1)){
dp[i][j]=min(dp[i-1][j-1], dp[i-1][j]+1,dp[i][j-1]+1);
}else{
dp[i][j]=min(dp[i-1][j-1]+1, dp[i-1][j]+1,dp[i][j-1]+1);
}
}
return dp[m][n];
}
private static int min(int i, int j, int k) {
int min=Math.min(i, j);
min=(min<k)?min:k;
return min;
}
}