動態規劃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;
}
}