算法——動態規劃篇

算法——動態規劃篇

視頻地址:https://www.bilibili.com/video/BV1ix411f7aNjava

課程說明算法

  • 前導技能編程

    • 遞歸,基本的暴力搜索(必會)
  • 動態規劃(Dynamic Programming)數組

    • 名字並沒有多大意義
  • 目標ide

    • 分析->Coding->AC
  • 課程開始函數

    • Leetcode 198

198. 打家劫舍

你是一個專業的小偷,計劃偷竊沿街的房屋。每間房內都藏有必定的現金,影響你偷竊的惟一制約因素就是相鄰的房屋裝有相互連通的防盜系統,若是兩間相鄰的房屋在同一夜被小偷闖入,系統會自動報警。優化

給定一個表明每一個房屋存放金額的非負整數數組,計算你在不觸動警報裝置的狀況下,可以偷竊到的最高金額。spa

示例 1:設計

輸入: [1,2,3,1]
輸出: 4
解釋: 偷竊 1 號房屋 (金額 = 1) ,而後偷竊 3 號房屋 (金額 = 3)。
     偷竊到的最高金額 = 1 + 3 = 4 。

示例 2:code

輸入: [2,7,9,3,1]
輸出: 12
解釋: 偷竊 1 號房屋 (金額 = 2), 偷竊 3 號房屋 (金額 = 9),接着偷竊 5 號房屋 (金額 = 1)。
     偷竊到的最高金額 = 2 + 9 + 1 = 12 。

思路:暴力破解法(遞歸算法)

public class Solution {
    public int solve(int idx,int[] nums){
        if(idx<0){
            return 0;
        }
        return Math.max(nums[idx]+solve(idx-2,nums),solve(idx-1,nums));
    }
    public int  rob(int[] nums){
        return solve(nums.length-1,nums);
    }
}

【注】代碼超時——\(O(2^n)\)

n-1->(n-3,n-4,n-5...)

n-2->(n-4,n-5...)

深度爲 \(n\)

\(O(2^n)\)

定義

  • 本質:遞歸

  • 原問題(N)->子問題(N-1)->原問題(N)

  • 最優子結構

    • 子問題最優決策可導出原問題最優決策
    • 無後效性(對後面的解(決策)不形成影響)
  • 重疊子問題

    • 去冗餘(重複計算)
    • 空間換時間(注意分析時空複雜度

遞歸的意義:遞歸不是追求效率,遞歸追求的是咱們怎麼看待、拆解這個問題。

處理重疊子問題:(用一個數組來存結果)——計劃搜索

public class Solution {
    public static int[] result;//數組存結果

    public int solve(int idx,int[] nums){
        if(idx<0){
            return 0;
        }

        if(result[idx]>=0){
            return result[idx];
        }//算過了直接從數組取值
        
        result[idx]=Math.max(nums[idx]+solve(idx-2,nums),solve(idx-1,nums));
        return result[idx];
    }
    public int  rob(int[] nums){
        result=new int[nums.length];
        for (int i = 0; i < nums.length; i++) {
            result[i]=-1;
        }//初始化數組置爲-1
        return solve(nums.length-1,nums);
    }
}

解:每一個狀態只算了一遍—— \(O(n)\)

問題共性

  • 套路

    • 最優、最大、最小、最長、計數
  • 離散問題

    • 容易設計狀態(整數0/1揹包問題)
  • 最優子結構

    • N-1能夠推到出N

基本步驟

  • 四個步驟
    • 設計暴力算法,找到冗餘
    • 設計並存儲狀態(一維,二維,三維數組,甚至用Map)
    • 遞歸式(狀態轉移方程)
    • 自底向上計算最優解(編程方式)

遞歸改遞推(非遞歸):

自頂向下的解法

public class Solution {
    public static int[] result;//數組存結果

    public int solve(int idx,int[] nums){
        if(idx<0){
            return 0;
        }

        if(result[idx]>=0){
            return result[idx];
        }//算過了直接從數組取值
        result[idx]=Math.max(nums[idx]+solve(idx-2,nums),solve(idx-1,nums));
        return result[idx];
    }
    public int  rob(int[] nums){
        if(nums.length==0){
            return 0;
        }
        if(nums.length==1){
            return nums[0];
        }
        result=new int[nums.length];
        result[0]=nums[0];
        result[1]=Math.max(nums[0],nums[1]);
        for (int idx = 2; idx < nums.length; ++idx) {
            result[idx]=Math.max(nums[idx]+result[idx-2],result[idx-1]);
        }
        return result[nums.length-1];
    }
}

實例

  • 斐波那契數列

    • 暴力遞歸
    • F(n)表示斐波那契第n個
    • F(n)=F(n-1)+F(n-2),if n>=2,otherwise F(n)=1
    • for i<- 2 to n
  • N!

    • 暴力遞歸
    • F(n)表示n!的值
    • F(n)=F(n-1)*n,if n>=1,otherwise F(n)=1
    • for i<- 2 to n

小兵向前衝

  • N*M的棋盤上,小兵要從左下角走到右上角,只能向上或者向右走,穩有多少種走法
  • 套路:計數問題
    • 暴力搜索(回溯法)
    • F(n,m)表示棋盤大小爲n*m時走法數量
    • F(n,m)=F(n-1,m)+F(n,m-1) if n*m>0 , otherwise F(n,m)=1
    • for i<- 1 to n
    • for j<- i to n

暴力破解法:

public class Solution {
   public int f(int n,int m){
       if(n==0 || m==0){
            return 0;
       }
       if(n==1 || m==1){
           return 1;
       }
       return f(n-1,m)+f(n,m-1);
   }
}

01揹包問題

  • 小偷有一個容量爲W的揹包,有n件物品,第i個物品價值vi,且重wi

  • 目標:找到xi是的對於全部的xi={0,1}​

  • \(sum(wi*xi)<=W\),而且\(sum(xi*vi)\)最大

  • 套路:最大

    • 暴力回溯法怎麼寫?
    • F(i,W)表示前i件物品的體積爲w,最大價值
    • F(i,W)=max{F(i-1,W),F(i-1,W-wi)+vi}//後一項當W<wi時爲0
    • for i<- 1 to W
    • for j<- 1 to n

Leetcode 322.零錢兌換

322.零錢兌換

給定不一樣面額的硬幣 coins 和一個總金額 amount。編寫一個函數來計算能夠湊成總金額所需的最少的硬幣個數。若是沒有任何一種硬幣組合能組成總金額,返回 -1。

示例 1:

輸入: coins = [1, 2, 5], amount = 11
輸出: 3 
解釋: 11 = 5 + 5 + 1

示例 2:

輸入: coins = [2], amount = 3
輸出: -1

暴力破解法:

public class Solution {
    public static int maxValue=10000000;
   public int search(int idx,int amount,int[] coins){
       if(amount==0){
           return 0;
       }
       if(amount<0){
           return maxValue;
       }
       if(idx>=coins.length){
           return maxValue;
       }
       return Math.min(search(idx,amount-coins[idx],coins)+1,search(idx+1,amount,coins));
   }
   public int coinChange(int[] coins,int amount){
       int res=search(0,amount,coins);
       if(res<maxValue){
           return res;
       }else{
           return -1;
       }
   }
}

超時——優化(空間換時間)

public class Solution {
    public static int maxValue=10000000;
    public static int[][] f=new int[1000][10000];
   public int search(int idx,int amount,int[] coins){
       if(amount==0){
           return 0;
       }
       if(amount<0){
           return maxValue;
       }
       if(idx>=coins.length){
           return maxValue;
       }
       if(f[idx][amount]>=0){
           return f[idx][amount];
       }

       f[idx][amount]=Math.min(search(idx,amount-coins[idx],coins)+1,search(idx+1,amount,coins));
       return f[idx][amount];
   }
   public int coinChange(int[] coins,int amount){
       for (int i = 0; i < 1000; i++) {
           for(int j=0;j<10000;j++){
               f[i][j]=-1;
           }
       }
       
       int res=search(0,amount,coins);
       if(res<maxValue){
           return res;
       }else{
           return -1;
       }
   }
}

遞歸轉遞推(非遞歸)省空間

 

最長公共子序列

  • 一個給定序列的子序列是在該序列中刪去若干元素後獲得的序列
  • 給定兩個序列X和Y,當另外一個序列Z既是X的子序列又是Y的子序列時,稱Z是序列X和Y的公共子序列
  • 最長公共子序列
  • X=(A,B,C,B,D,A,B)Y=(B,D,C,A,B,A)
  • (B,C,B,A) (B,D,A,B)

相似LeetCode 72

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')

僞代碼

X[n] 取哪些
Y[m] 取哪些

int search(int xi,int yi){
	if(xi>n || yi>m){
		return -1;
	}
	if(xi==n && yi==m){
		return 0;
	}
	return  Math.max(
	if(X[xi]==Y[yi])
		search(xi+1,yi+1)+1,
	search(xi,yi+1),
	search(xi+1,yi)
	);
	//search(xi,yi);
}

旅行商問題

  • 一個商人要不重複的訪問N個城市,容許從任意城市出發,在任意城市結束。現已知任意兩個城市之間的道路長度
  • 求城市的訪問序列,使得商人走過的路程最短
  • 例:N=4,訪問序列3,4,1,2
  • NP問題,最優解無多項式時間算法
  • 時間複雜度?空間複雜度?
  • 狀態壓縮
    • 時間複雜度
    • 空間複雜度

暴力搜索

map[n][n];
map[i][j]-> i-j距離
boolean visit[n];
int search(int idx,int count){
	if(count==n){
	return 0;
	}
	int min=1000000;
	for(int i=0;i<n;i++){
        if(!visit[i]){
        	visit[i]=true;
            int t=search(i,count+1)+map[idx][i];
                if(t<min){
                    min=t;
                }
        }
        visit[i]=false;//復原
	}
	return min;
}

總結

  • 動態規劃 算法用到的題目存在不少套路

  • 滾動數組,狀態壓縮,升維,單調性,四邊形不等式(高級套路)

  • 先學套路,跳出套路

  • 本質:先暴力,找冗餘,去冗餘

相關文章
相關標籤/搜索