奇妙的算法【1】-動態規劃算法

 

1,菲波那契數列

  斐波那契數列(Fibonacci sequence),又稱黃金分割數列、因數學家列昂納多·斐波那契(Leonardoda Fibonacci)以兔子繁殖爲例子而引入,故又稱爲「兔子數列」,指的是這樣一個數列:一、一、二、三、五、八、1三、2一、3四、……在數學上,斐波納契數列以以下被以遞推的方法定義:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)在現代物理、準晶體結構、化學等領域,斐波納契數列都有直接的應用html

   兔子生兔子問題,兔子數列。java

1.1 生兔子問題

  古典問題:有一對兔子,從出生後第3個月起每月都生一對兔子,小兔子長到第四個月後每月又生一對兔子,假如兔子都不死,問每月的兔子總數爲多少git

  兔子的規律爲數列1,1,2,3,5,8,13,21....github

package com.cnblogs.mufasa.Fibonacci;

public class Answer1_Rabite {
    //最普通的遞歸調用,會作出不少次重複計算
    public static int solution1(int x){
        if(x==1 || x==2){
            return 1;
        }else {
            return solution1(x-1)+solution1(x-2);
        }
    }
    //動態規劃,速度很快【逆推導】
    public static int solution2(int x){
        if(x==1 || x==2){
            return 1;
        }
        int a=1,b=1,c=0;
        for(int i=3;i<=x;i++){
            c=a+b;
            a=b;
            b=c;
        }
        return c;
    }
    
}

 

1.2 爬樓梯問題

  有 N 階樓梯,每次能夠上一階或者兩階,求有多少種上樓梯的方法。算法

package com.cnblogs.mufasa.Fibonacci;

public class Answer2_climbStairs {

    public static int solution1(int x){//遞歸解法
        if(x<=2){
            return x;
        }
        return solution1(x-1)+solution1(x-2);
    }

    public static int solution2(int x){//動態規劃
        if(x<=2){
            return x;
        }
        int a=1,b=2,c=0;//與生兔子問題不一樣,這裏起始有變化
        for(int i=3;i<=x;i++){
            c=a+b;
            a=b;
            b=c;
        }
        return c;
    }
}

 

1.3 強盜搶劫

  https://leetcode-cn.com/problems/house-robber-ii/數組

  搶劫一排住戶,可是不能搶鄰近的住戶,求最大搶劫量ide

values={1,2,3,11,12,3,1,12};spa

package com.cnblogs.mufasa.Fibonacci;

public class Answer3_Robber {

    //最普通的遞歸調用,會作出不少次重複計算
    public static int solution1(int[] values,int index){
        int x=values.length;
        if(x-index==1){
            return values[index];
        }else if(x-index==2) {
            return (values[index] > values[index-1] ? values[index] : values[index-1]);
        }
        int a=solution1(values,index+1);//不搶這一家
        int b=values[index]+solution1(values,index+2);//搶這一家
        return (a>b?a:b);
    }

    //動態規劃,速度很快【逆推導】
    public static int solution2(int[] values){
        int x=values.length;
        if(x==1){
            return values[0];
        }else if(x==2) {
            return (values[0] > values[1] ? values[0] : values[1]);
        }
        int a=values[0],b=(values[0]>values[1]?values[0]:values[1]),c=0;
        for(int i=2;i<x;i++){
            c=(a+values[i]>b?a+values[i]:b);
            a=b;
            b=c;
        }
        return c;
    }
}

 

1.4 強盜在環形街區搶劫

  https://leetcode.com/problems/house-robber-ii/description/.net

  你是一個專業的小偷,計劃偷竊沿街的房屋,每間房內都藏有必定的現金。這個地方全部的房屋都圍成一圈,這意味着第一個房屋和最後一個房屋是緊挨着的。同時,相鄰的房屋裝有相互連通的防盜系統,若是兩間相鄰的房屋在同一夜被小偷闖入,系統會自動報警。3d

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

package com.cnblogs.mufasa.Fibonacci;

public class Answer4_Robber2 {

    //最普通的遞歸調用,會作出不少次重複計算
    public static int solution1(int[] values,int index0,int index1){//起始位置、終止
        int x=values.length;
        if(x-index1-index0==1){
            return values[index0];
        }else if(x-index1-index0==2) {
            return (values[index0] > values[index0-1] ? values[index0] : values[index0-1]);
        }
        int a=solution1(values,index0+1,index1);//不搶這一家
        int b=values[index0]+solution1(values,index0+2,index1);//搶這一家
        return (a>b?a:b);
    }

    //動態規劃,速度很快【逆推導】
    public static int solution2(int[] values){
        if(values==null || values.length==0){
            return 0;
        }
        int x=values.length;
        if(x==1){
            return values[0];
        }
        int a=singleDeal(values,0,1);
        int b=singleDeal(values,1,0);
        return (a>b?a:b);
    }

    private static int singleDeal(int[] values,int index0,int index1){//
        int x=values.length;
        if(x-index1-index0==1){
            return values[index0];
        }else if(x-index1-index0==2) {
            return (values[index0] > values[index0+1] ? values[index0] : values[index0+1]);
        }
        int a=values[index0],b=(values[index0]>values[index0+1]?values[index0]:values[index0+1]),c=0;
        for(int i=index0+2;i<x-index1;i++){
            c=(a+values[i]>b?a+values[i]:b);
            a=b;
            b=c;
        }
        return c;
    }
}

 

1.5 信件錯排

  http://www.javashuo.com/article/p-ebaqvsjx-bb.html

  有 N 個 信 和 信封,它們被打亂,有多少種狀況是全部人都收到了錯誤的郵件?

   當n個編號元素放在n個編號位置,元素編號與位置編號各不對應的方法數用dp[n]表示,那麼dp[n-1]就表示n-1個編號元素放在n-1個編號位置,各不對應的方法數,其它類推.
  第一步,把第n個元素放在一個位置,好比位置k,一共有n-1種方法;
  第二步,放編號爲k的元素,這時有兩種狀況:⑴把它放到位置n,那麼,對於剩下的n-1個元素,因爲第k個元素放到了位置n,剩下n-2個元素就有dp[n-2]種方法;⑵第k個元素不把它放到位置n,這時,對於這n-1個元素,有dp[n-1]種方法;

package com.cnblogs.mufasa.Fibonacci;

public class Answer5_mail {
    //最普通的遞歸調用,會作出不少次重複計算
    public static int solution1(int x){
        if(x<=1){
            return 0;
        }else if(x==2){
            return 1;
        }
        return (x-1)*(solution1(x-1)+solution1(x-2));
    }


    //動態規劃,速度很快【逆推導】
    public static int solution2(int x){
        if(x<=1){
            return 0;
        }else if(x==2){
            return 1;
        }
        int a=0,b=1,c=0;
        for(int i=3;i<=x;i++){
            c=(i-1)*(a+b);
            a=b;
            b=c;
        }
        return c;
    }
}

 

1.6 母牛生產【與兔子生兔子類似】

  題目描述:假設農場中成熟的母牛每一年都會生 1 頭小母牛,而且永遠不會死。第一年有 1 只小母牛,從第二年開始,母牛開始生小母牛。每隻小母牛 3 年以後成熟又能夠生小母牛。給定整數 N,求 N 年後牛的數量。

package com.cnblogs.mufasa.Fibonacci;

public class Answer6_cow {
    //最普通的遞歸調用,會作出不少次重複計算
    public static int solution1(int x){
        if(x<=4){
            return x;
        }else {
            return solution1(x-1)+solution1(x-3);
        }
    }
    //動態規劃,速度很快【逆推導】
    public static int solution2(int x){
        if(x<=4){
            return x;
        }
        int a=2,b=3,c=4,d=0;
        for(int i=5;i<=x;i++){
            d=a+c;
            a=b;
            b=c;
            c=d;
        }
        return d;
    }
}

 

驗證程序:

package com.cnblogs.mufasa.Fibonacci;

public class Client {
    public static void main(String[] args) {

//        System.out.println(Answer1_Rabite.solution1(8));//兔子問題,遞歸解法
//        System.out.println(Answer1_Rabite.solution2(8));//兔子問題,動態規劃

//        System.out.println(Answer2_climbStairs.solution1(5));//爬樓梯問題,遞歸解法
//        System.out.println(Answer2_climbStairs.solution2(5));//爬樓梯問題,動態規劃

//        System.out.println(Answer3_Robber.solution1(new int[]{1,2,3,11,12,3,1,12},0));//強盜問題,遞歸解法
//        System.out.println(Answer3_Robber.solution2(new int[]{1,2,3,11,12,3,1,12}));//強盜問題,動態規劃

//        int a=Answer4_Robber2.solution1(new int[]{1,2,3,11,12,3,1,12},0,1);//環形強盜問題,遞歸解法
//        int b=Answer4_Robber2.solution1(new int[]{1,2,3,11,12,3,1,12},1,0);
//        System.out.println((a>b?a:b));//環形強盜問題,遞歸解法
//        System.out.println(Answer4_Robber2.solution2(new int[]{1,2,3,11,12,3,1,12}));//環形強盜問題,動態規劃

//        System.out.println(Answer5_mail.solution1(5));//信件問題,遞歸解法
//        System.out.println(Answer5_mail.solution2(5));//信件問題,動態規劃

//        System.out.println(Answer6_cow.solution1(8));//母牛問題,遞歸解法
//        System.out.println(Answer6_cow.solution2(8));//母牛問題,動態規劃

    }
}

 

2,矩陣路徑【有以前的一維轉變到二維平面】

  2.1 最小路徑和

  https://leetcode-cn.com/problems/minimum-path-sum/

  給定一個包含非負整數的 m x n 網格,請找出一條從左上角到右下角的路徑,使得路徑上的數字總和爲最小。

package com.cnblogs.mufasa.Matrix_path;

public class Answer1_minPath {
    public static int Solution1(int[][] grid) {//遞歸調用,算法複雜度O(2^(m+n)),空間複雜度爲O(m+n)
        return calculate(grid, 0, 0);
    }
    private static int calculate(int[][] grid, int i, int j){
        if (i == grid.length || j == grid[0].length) return Integer.MAX_VALUE;//沒法到達此位置
        if (i == grid.length - 1 && j == grid[0].length - 1) return grid[i][j];//此位置爲目的地
        return grid[i][j] + Math.min(calculate(grid, i + 1, j), calculate(grid, i, j + 1));//相似於二叉樹分裂
    }

    public static int Solution2(int[][] grid) {//動態規劃,二維
        if(grid.length==0||grid[0].length==0){
            return 0;
        }
        int m=grid.length,n=grid[0].length;
        int[][] dp=new int[m][n];
        for (int i = m - 1; i >= 0; i--) {
            for (int j = n - 1; j >= 0; j--) {
                if (i == grid.length - 1 && j != grid[0].length-1) {
                    dp[i][j] = grid[i][j] + dp[i][j + 1];
                } else if (i != grid.length - 1 && j == grid[0].length-1) {
                    dp[i][j] = grid[i][j] + dp[i + 1][j];
                } else if (i != grid.length - 1 && j != grid[0].length-1) {
                    dp[i][j] = grid[i][j] + Math.min(dp[i + 1][j], dp[i][j + 1]);
                } else {
                    dp[i][j] = grid[i][j];
                }
            }
        }
        return dp[0][0];
    }

    public static int Solution3(int[][] grid) {//一維動態規劃
        if (grid.length == 0 || grid[0].length == 0) {
            return 0;
        }
        int m = grid.length, n = grid[0].length;
        int[] dp = new int[n];
        for(int i = m-1; i >=0; i--){
            for(int j = n-1; j >=0; j--){
                if(i==m-1&&j!=n-1){//水平移動
                    dp[j]=grid[i][j]+dp[j+1];
                }else if(i!=m-1&&j==n-1){//上下移動
                    dp[j]=grid[i][j]+dp[j];
                }else if(i!=m-1&&j!=n-1){
                    dp[j]=grid[i][j]+Math.min(dp[j],dp[j+1]);
                }else {
                    dp[j]=grid[i][j];
                }
            }
        }
        return dp[0];
    }

    public static int Solution4(int[][] grid) {//動態規劃,本質與上一個一維動態規劃一致
        if (grid.length == 0 || grid[0].length == 0) {
            return 0;
        }
        int m = grid.length, n = grid[0].length;
        int[] dp = new int[n];

        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (j == 0) {
                    dp[j] = dp[j];        // 只能從上側走到該位置
                } else if (i == 0) {
                    dp[j] = dp[j - 1];    // 只能從左側走到該位置
                } else {
                    dp[j] = Math.min(dp[j - 1], dp[j]);
                }
                dp[j] += grid[i][j];
            }
        }
        return dp[n - 1];
    }

    public static int Solution5(int[][] grid) {//動態規劃,不開闢新空間,可是會修改元數組內容,本質仍是同樣
        if (grid.length == 0 || grid[0].length == 0) {
            return 0;
        }
        int m = grid.length, n = grid[0].length;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if(i==0&&j!=0){
                    grid[i][j]=grid[i][j-1]+grid[i][j];
                }else if(i!=0&&j==0){
                    grid[i][j]=grid[i-1][j]+grid[i][j];
                }else if(i!=0&&j!=0){
                    grid[i][j]=grid[i][j]+Math.min(grid[i-1][j],grid[i][j-1]);
                }
            }
        }
        return grid[m-1][n-1];
    }
}

 

 2.2 矩陣的總路徑數

  一個機器人位於一個 m x n 網格的左上角 (起始點在下圖中標記爲「Start」 )。

  機器人每次只能向下或者向右移動一步。機器人試圖達到網格的右下角(在下圖中標記爲「Finish」)。

  問總共有多少條不一樣的路徑?

package com.cnblogs.mufasa.Matrix_path;

import java.util.Arrays;

public class Answer2_multiple {

    public static int solution1(int m, int n){//遞歸調用
        if(m==1&&n==1){//原地不動
            return 1;
        }
        if(m>1&&n>1){
            return solution1(m-1, n)+solution1(m, n-1);
        }else {
            return 1;
        }
    }

    public static int solution2(int m, int n){//動態規劃,一維
        int[] dp = new int[n];
        Arrays.fill(dp,1);
        for (int i = 1; i < m; i++) {
            for (int j = 1; j < n; j++) {
                dp[j] = dp[j] + dp[j - 1];
            }
        }
        return dp[n-1];
    }
}

 

驗證程序:

package com.cnblogs.mufasa.Matrix_path;

public class Client {
    public static void main(String[] args) {
//        int[][] grid={{1,3,4,8},{3,2,2,4},{5,7,1,9},{2,3,2,3},{2,3,2,3}};
//        System.out.println(Answer1_minPath.Solution1(grid));//遞歸調用
//        System.out.println(Answer1_minPath.Solution2(grid));//二維動態規劃
//        System.out.println(Answer1_minPath.Solution3(grid));//一維動態規劃
//        System.out.println(Answer1_minPath.Solution4(grid));//一維動態規劃
//        System.out.println(Answer1_minPath.Solution5(grid));//動態規劃,不開闢空間

        System.out.println(Answer2_multiple.solution1(3,7));
        System.out.println(Answer2_multiple.solution2(3,7));

    }
}
View Code

 

3,數組區間

 

4,揹包問題

①時間-任務-價值問題:https://blog.csdn.net/houboTech/article/details/79689157

視頻講解:https://www.bilibili.com/video/av16544031?from=search&seid=4083931774448408088

這裏使用三種解題思路:①遞歸算法;②登記表【overlap sub-problem重疊子問題】;③動態規劃。

package com.cnblogs.mufasa.demo1;

import java.util.HashMap;

public class Time_value {
    private final int num=8;
    private final int[][] times=new int[][] {{1,4},{3,5},{0,6},{4,7},{3,8},{5,9},{6,10},{8,11}};
    private final int[] values={5,1,8,4,6,3,2,4};
    private final int[] prevs=preNode(times);

    //遞歸法,時間複雜度爲O(2^n)
    public int rec_opt(int i) {
        if(i<=0)
            return 0;
        if(i==1)
            return values[i-1];
        int choice = values[i-1] + rec_opt(prevs[i-1]);
        int notChoice = rec_opt(i-1);
        return max(notChoice,choice);
    }
    
    //登記表形式,算法複雜度(n)【本質是將重複子問題進行記錄防止重複計算】
    HashMap<Integer,Integer> hm=new HashMap<>(50);
    public int dict_opt(int i){
        if(hm.containsKey(i)){
            return hm.get(i);
        }
        int outNum=0;
        if(i==1){
            outNum= values[i-1];
        }else {
            outNum=max(values[i-1] + rec_opt(prevs[i-1]),rec_opt(i-1));
        }
        hm.put(i,outNum);
        return outNum;
    }

    //動態規劃,時間複雜度爲O(n),本質上與登記表形式相同,可是代碼更加優雅
    public int dp_opt() {//求作任務能夠獲得的最大價值(動態規劃法)
        int[] opt = new int[values.length + 1];
        opt[0] = 0;
        opt[1] = values[0];
        for (int i = 1; i < opt.length; i++) {
            int choice = values[i - 1] + opt[prevs[i - 1]];
            int notChoice = opt[i - 1];
            opt[i] = max(choice, notChoice);
        }
        return opt[opt.length - 1];
    }



    //輔助方法
    private int[] preNode(int[][] times){//獲取prevs數據【成功】
        int[] prevs=new int[times.length];
        for(int i=1;i<times.length;i++){
            for(int j=i-1;j>=0;j--){
                if(times[j][1]<=times[i][0]){
                    prevs[i]=j+1;
                    break;
                }
            }
        }
        return prevs;
    }
    private static int max(int a,int b){
        return (a>=b?a:b);
    }

    //算法驗證
    public static void main(String[] args) {
        Time_value tv=new Time_value();
        int result1=tv.rec_opt(8);
        int result2=tv.dp_opt();
        int result3=tv.dict_opt(8);
        System.out.println(result1);
        System.out.println(result2);
        System.out.println(result3);
    }
}

 參考連接:

https://github.com/CyC2018/CS-Notes/blob/master/notes/Leetcode%20%E9%A2%98%E8%A7%A3%20-%20%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92.md

相關文章
相關標籤/搜索