奇妙的算法【1】-动态规划算法

 

1,菲波那契数列

  斐波那契数列(Fibonacci sequence),又称黄金分割数列、因数学家列昂纳多·斐波那契(Leonardoda Fibonacci)以兔子繁殖为例子而引入,故又称为“兔子数列”,指的是这样一个数列:1、1、2、3、5、8、13、21、34、……在数学上,斐波纳契数列以如下被以递推的方法定义:F(1)=1,F(2)=1, F(n)=F(n-1)+F(n-2)(n>=3,n∈N*)在现代物理、准晶体结构、化学等领域,斐波纳契数列都有直接的应用

   兔子生兔子问题,兔子数列。

1.1 生兔子问题

  古典问题:有一对兔子,从出生后第3个月起每个月都生一对兔子,小兔子长到第四个月后每个月又生一对兔子,假如兔子都不死,问每个月的兔子总数为多少

  兔子的规律为数列1,1,2,3,5,8,13,21....

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/

  抢劫一排住户,但是不能抢邻近的住户,求最大抢劫量

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

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/

  你是一个专业的小偷,计划偷窃沿街的房屋,每间房内都藏有一定的现金。这个地方所有的房屋都围成一圈,这意味着第一个房屋和最后一个房屋是紧挨着的。同时,相邻的房屋装有相互连通的防盗系统,如果两间相邻的房屋在同一晚上被小偷闯入,系统会自动报警。

  给定一个代表每个房屋存放金额的非负整数数组,计算你在不触动警报装置的情况下,能够偷窃到的最高金额。

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 信件错排

  https://www.cnblogs.com/yjxyy/p/11116364.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

posted on 2019-08-22 16:44  周健康  阅读(608)  评论(0编辑  收藏  举报

导航