什麼是動態規劃
以下是我綜合了動態規劃的特點給出的動態規劃的定義:
動態規劃是一種多階段決策最優解模型,一般用來求最值問題,多數情況下它可以採用自下而上的遞推方式來得出每個子問題的最優解(即最優子結構),進而自然而然地得出依賴子問題的原問題的最優解。
劃重點:
多階段決策,意味着問題可以分解成子問題,子子問題,。。。,也就是說問題可以拆分成多個子問題進行求解
最優子結構,在自下而上的遞推過程中,我們求得的每個子問題一定是全局最優解,既然它分解的子問題是全局最優解,那麼依賴於它們解的原問題自然也是全局最優解。
自下而上,怎樣才能自下而上的求出每個子問題的最優解呢,可以肯定子問題之間是有一定聯繫的,即迭代遞推公式,也叫「狀態轉移方程」,要定義好這個狀態轉移方程, 我們就需要定義好每個子問題的狀態(DP 狀態),那爲啥要自下而上地求解呢,因爲如果採用像遞歸這樣自頂向下的求解方式,子問題之間可能存在大量的重疊,大量地重疊子問題意味着大量地重複計算,這樣時間複雜度很可能呈指數級上升(在下文中我們會看到多個這樣重複的計算導致的指數級的時間複雜度),所以自下而上的求解方式可以消除重疊子問題。
上面可能很難看到,但重要的是理解狀態與策略,狀態就是當前的與結果相關的一種定義,策略是改變當前狀態的方法,而我們要做到就是遍歷所有策略選擇最佳策略。物理上的Max或者Min.
LeetCode 62. 不同路徑
dp[i][j]代表到i,j位置有多少種走法,所有初始狀態dp[i][0]=1,dp[0][i]=1。
因爲只能從上往下走或者從左往右走,所有動態轉移:
dp[i][j]=dp[i][j-1]+dp[i-1][j]
class Solution {
public int uniquePaths(int m, int n) {
int[][] dp = new int[m][n];
for(int i=0;i<n;i++){
dp[0][i]=1;
}
for(int i=0;i<m;i++){
dp[i][0]=1;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
dp[i][j]=dp[i][j-1]+dp[i-1][j];
}
}
return dp[m-1][n-1];
}
}
LeetCode 63. 不同路徑 II
有條件動態規劃,在上一題的基礎上處理好障礙條件。
class Solution {
public int uniquePathsWithObstacles(int[][] obstacleGrid) {
int m = obstacleGrid.length;
int n = obstacleGrid[0].length;
int[][] dp = new int[m][n];
for(int i=0;i<n;i++){
if(obstacleGrid[0][i]==1){
break;
}
dp[0][i]=1;
}
for(int i=0;i<m;i++){
if(obstacleGrid[i][0]==1){
break;
}
dp[i][0]=1;
}
for(int i=1;i<m;i++){
for(int j=1;j<n;j++){
if(obstacleGrid[i][j]==1){
dp[i][j]=0;
continue;
}
dp[i][j]=dp[i][j-1]+dp[i-1][j];
}
}
return dp[m-1][n-1];
}
}
LeetCode 64. 最小路徑和
dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];
class Solution {
public int minPathSum(int[][] grid) {
int[][] dp = new int[grid.length][grid[0].length];
int temp=0;
for(int i=0;i<grid.length;i++){
temp+=grid[i][0];
dp[i][0]=temp;
}
temp=0;
for(int i=0;i<grid[0].length;i++){
temp+=grid[0][i];
dp[0][i]=temp;
}
for(int i=1;i<grid.length;i++){
for(int j=1;j<grid[0].length;j++){
dp[i][j]=Math.min(dp[i-1][j],dp[i][j-1])+grid[i][j];
}
}
return dp[grid.length-1][grid[0].length-1];
}
}
LeetCode 300. 最長上升子序列
動態規劃基礎問題,dp[i]表示以num[i]結尾的最長上升子序列是多少。
狀態 轉移方程爲 當num[j]<num[i]時
dp[i]=Math.max(dp[i],dp[j]+1);
class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
int result = 0;
for(int i=0;i<nums.length;i++){
dp[i]=1;
for(int j=0;j<i;j++){
if(nums[j]<nums[i]){
dp[i]=Math.max(dp[i],dp[j]+1);
}
}
if(dp[i]>result){
result = dp[i];
}
}
return result;
}
}
經典題目編輯距離,自頂而加的思維方式比較容易理解動態轉移方程。
當s1[i] == s2[j] 啥也不做 dp(i - 1, j - 1)
其他三種情況爲,替換,刪除,插入
dp[i-1][j-1]
dp[i][j-1]
dp[i-1][j]
class Solution {
public int minDistance(String word1, String word2) {
int[][] dp = new int[word1.length()+1][word2.length()+1];
for(int i=0;i<=word1.length();i++){
dp[i][0]=i;
}
for(int i=0;i<=word2.length();i++){
dp[0][i]=i;
}
for(int i=1;i<=word1.length();i++){
for(int j=1;j<=word2.length();j++){
if(word1.charAt(i-1)==word2.charAt(j-1)){
dp[i][j]=dp[i-1][j-1];
}else {
dp[i][j]=min(dp[i-1][j],dp[i][j-1],dp[i-1][j-1])+1;
}
}
}
return dp[word1.length()][word2.length()];
}
public int min(int a, int b, int c) {
return Math.min(a, Math.min(b, c));
}
}
找好條件和狀態問題。
class Solution {
public int numDecodings(String s) {
int[] dp = new int[s.length()];
if(s.charAt(0)=='0'){
return 0;
}
dp[0]=1;
for(int i=1;i<s.length();i++){
String temp = s.substring(i-1,i+1);
if(s.charAt(i)=='0'){
if(s.charAt(i-1)!='1'&&s.charAt(i-1)!='2'){
return 0;
}
if(i-2<0){
dp[i] = 1;
}else {
dp[i] = dp[i-2];
}
continue;
}
if(Integer.valueOf(temp)<=26&&temp.charAt(0)!='0'){
if(i-2<0){
dp[i] = dp[i - 1] +1;
}else {
dp[i] = dp[i - 1] + dp[i-2];
}
}else {
dp[i]=dp[i-1];
}
}
return dp[s.length()-1];
}
}
LeetCode 56. 合併區間
貪心算法一種,先排序數組,然後取最早開始的。按規則合併。
class Solution {
class Interval {
int startinter;
int endinter;
}
public int[][] merge(int[][] intervals) {
if(intervals==null||intervals.length==0){
return intervals;
}
Arrays.sort(intervals, new Comparator<int[]>() {
@Override
public int compare(int[] o1, int[] o2) {
return o1[0]-o2[0];
}
});
List<Interval> intervalList = new ArrayList<>();
int startinter = intervals[0][0];
int endinter = intervals[0][1];
for(int i=1;i<intervals.length;i++){
if(intervals[i][0]>endinter){
Interval interval = new Interval();
interval.endinter=endinter;
interval.startinter=startinter;
intervalList.add(interval);
startinter = intervals[i][0];
endinter = intervals[i][1];
}else {
if (startinter > intervals[i][0]) {
startinter = intervals[i][0];
}
if (endinter < intervals[i][1]) {
endinter = intervals[i][1];
}
}
}
Interval interval = new Interval();
interval.endinter=endinter;
interval.startinter=startinter;
intervalList.add(interval);
int[][] result = new int[intervalList.size()][2];
for(int i=0;i<intervalList.size();i++){
result[i][0]= intervalList.get(i).startinter;
result[i][1]= intervalList.get(i).endinter;
}
return result;
}
}
class Solution {
class Node {
int fir;
int sec;
}
public boolean PredictTheWinner(int[] nums) {
int n = nums.length;
if(n==1)
return true;
Node[][] dp = new Node[n][n];
for(int i=0;i<n;i++){
dp[i] = new Node[n];
dp[i][i] =new Node();
dp[i][i].fir = nums[i];
}
for(int i = n-2;i>=0;i--){
for (int j = i+1;j<n;j++){
int left = nums[i]+dp[i+1][j].sec;
int right = nums[j]+dp[i][j-1].sec;
dp[i][j] =new Node();
if(left > right){
dp[i][j].fir = left;
dp[i][j].sec = dp[i+1][j].fir;
}else {
dp[i][j].fir = right;
dp[i][j].sec = dp[i][j-1].fir;
}
}
}
return dp[0][n-1].fir >= dp[0][n-1].sec;
}
}