算法 动态规划

一、动态规划入门

1.1 array代表数组,数组中的数是正数,代表钱数,每种面值的货币可以使任意张,给定一aim代表要找的钱数,求换钱有多少种方法。比如:arr={5,10,25,1},aim=1000

暴力搜索:一般使用暴力递归
记忆搜索:将每次递归的结果放在hashmap中,下次递归的时候去hash表中查找结果,有,使用记忆的结果,没有则再进行递归。记忆搜索不规定计算的顺序。
经典的动态规划方法:动态规划规定记忆的顺序。

  • 如果arr的长度为n,生成列数为aim+1的矩阵dp。dp[i][j]的含义是在使用arr[0,…i]的情况下,组成钱数j有多少种方法。
  • 如果完全不用arr[i]货币,只使用arr[0…i-1]货币时,方法数为dp[i-1][j]
  • 使用一张arr[i]货币,剩下的钱用arr[0…i-1]货币组成时,方法数为dp[i-1][j-1*arr[i]]
  • 使用n张arr[i]货币,剩下的钱用arr[0…i-1]货币组成时,方法数为dp[i-1][j-n*arr[i]]

算法:

  • dp[i][j] = dp[i] + dp[i-1][j-arr[i]] + dp[i-1][j-arr[i]*2] + … + dp[i-1][j-array[i]*n];
  • n = j / arr[i];

暴力搜索:

public class find {
    public static void main(String[] args) {
        int[] ints = new int[]{5, 10, 20, 100};
        System.out.println(process1(ints, 0,100));
    }

    public static int process1(int[] arr,int index,int aim)
    {
        int res =0;
        if(index==arr.length - 1)
            res = aim==0?1:0;
        else
        {
            for(int i=0;arr[index]*i<=aim;i++)  
                res += process1(arr,index+1,aim-arr[index]*i);
        }
        return res;
    }
}

记忆搜索:

public class find {
    public static void main(String[] args) {
        int[] ints = new int[]{5, 10, 20, 100};
        System.out.println(coins1(ints,100));
    }
    public static int coins1(int[] arr,int aim)
    {
        int[][] map = new int[arr.length][aim + 1];
        if(arr==null||arr.length==0||aim<0)
            return 0;
        return process1(arr,0,aim,map);
    }
    public static int process1(int[] arr,int index,int aim, int[][] map) {
        int res = 0;
        int mapValue = 0;
        if (index == arr.length - 1) {
            res = aim == 0 ? 1 : 0;
        } else {
            for (int i = 0; arr[index] * i <= aim; i++) {
                mapValue = map[index + 1][aim - arr[index] * i];
                if(mapValue != 0){
                    res += mapValue;
                }else {
                    res += process1(arr, index + 1, aim - arr[index] * i, map);
                }
            }
        }
        return res;
    }
}

参考:动态规划算法案例分析

  • 动态规划:
public class find {
    public static void main(String[] args) {
        int[] ints = new int[]{5, 10, 20, 100};
        System.out.println(getCount(ints,3,100));
    }
    public static int getCount(int[] penny,int n, int aim) {
        // write code here
        int[][] dp = new int[n][aim+1];
        //矩阵初始化
        for(int i=0;i<n;i++)
        {
            for(int j=0;j<aim+1;j++)
            {
                dp[i][j]=0;
            }
        }
        //初始化第一行数值
        for(int j=0;j<aim+1;j++)
        {
            if(j%penny[0]==0)
                dp[0][j]=1;
        }
        //初始化第一列数值
        for(int i=0;i<n;i++)
        {
            dp[i][0]=1;
        }
        //求其他行数值
        for(int i=1;i<n;i++)
        {
            for(int j=1;j<aim+1;j++)
            {

                for(int k=0;(j-k*penny[i])>=0;k++)
                {
                    //根据上面分析的公式
                    dp[i][j] += dp[i-1][j-k*penny[i]];
                }
            }
        }
        return dp[n-1][aim];
    }
}

二、动态规划经典例题

2.1 有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法。

暴力搜索:

public class literator {
    public static void main(String[] args) {
        System.out.println(method(5));
    }
    public static int method(int i){
        if(i == 0){
            return 0;
        }
        if(i == 1){
            return 1;
        }
        if(i == 2){
            return 2;
        }
        else {
           return method(i - 1) + method(i - 2);
        }
    }
}

2.2 给定一个矩阵m,从左上角开始,每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中,最小的路径和,,如果给定的路径如下图,则最小的路径和应该为1,3,1,0,6,1,0这条路径,返回最小值为:12在这里插入图片描述

  • 建立一个二维数组array,array[i][j]代表从0,0出发到array[i][j]的最小值
  • 则到达array[i][j]最小值的情况是从上面或者从左边来的最小值加上array[i][j]的值构成的。
public class literator {
    public static void main(String[] args) {
        int[][] array = new int[][]{{1,3,5,9},{8,1,3,4},{5,0,6,1},{8,8,4,0}};
        System.out.println(method(array, 3, 3));
    }
    public static int method(int[][] array, int row, int cow){
          int[][] dp = new int[array.length][array[1].length];
          dp[0][0] = array[0][0];
        for (int i = 1; i < array[1].length; i++) {
            dp[0][i] = array[0][i] + array[0][i-1];
        }
        for (int i = 1; i < array.length; i++) {
            dp[i][0] = array[i][0] + array[i-1][0];
        }
        for (int i = 1; i < array.length; i++) {
            for (int i1 = 1; i1 < array[1].length; i1++) {
                dp[i][i1] = dp[i-1][i1] < dp[i][i1-1] ? dp[i-1][i1] + array[i][i1]:dp[i][i1-1] + array[i][i1];
            }
        }
        return dp[row][cow];
    }
}

2.3 返回数组arr的最长递增子序列

思路

  • 原数组int[] array
  • 建立一个数组int[] test,test[i]表示以array[i]为结尾的最大子序列长度。
  • test[i]的求法是比array[i]小的最大的的test[j]的值加1。

注意:是以array[i]为结尾的最大子序列长度,那就是比他小的array[j]中选出最大的test[j]值再加1。

public class literator {
    public static void main(String[] args) {
        int[]array = new int[]{2,1,3,42,45,2,6,7,2,2,3,4,5,6};
        System.out.println(method(array));
    }
    public static int method(int[] array){
          int[] compare = new int[array.length];
          compare[0] = 1;
        for (int i = 1; i < compare.length; i++) {
            int flag = 0;
            for (int j = 0; j < i; j++) {
                if(array[j] < array[i] && compare[j] > flag){
                    flag = compare[j];
                }
            }
            compare[i] = flag + 1;
        }
        int flag = 0;
        for (int i = 0; i < compare.length; i++) {
            if(flag < compare[i]){
                flag = compare[i];
            }
        }
        return flag;
    }
}

2.4 返回两个字符串的最大公共子序列

  • 字符串一长度为m
  • 字符串二长度为n
  • 生成m*n的二维数组,int[][] array,array[i][j]表示子符串m的第(i+1)个字符,符串m的第(j+1)个字符为结尾的最大公共子序列长度。

思路:

  • array[i][j]有三种来源:
    array[i-1][j]
    array[i][j-1]
    array[i-1][j-1],如果此时(str1.charAt(i) == str2.charAt(j))为true,则flag = array[i-1][j-1] + 1,array[i][j]是上述三种来源最大的值。
public class literator {
    public static void main(String[] args) {
        String str1 = "abc123def";
        String str2 = "123abcdef";
        System.out.println(method(str1, str2));
    }
    public static int method(String str1, String str2){
        int n = str1.length();
        int m = str2.length();
        int[][] array = new int[n][m];
        if(str1.charAt(0) == str2.charAt(0)){
            array[0][0] = 1;
        }else {
            array[0][0] = 0;
        }
        for (int i = 1; i < n; i++) {
            if(str2.charAt(0) == str1.charAt(i) || array[i-1][0] == 1)
            array[i][0] = 1;
        }
        for (int i = 1; i < m; i++) {
            if(str1.charAt(0) == str2.charAt(i) || array[0][i-1] == 1)
                array[0][i] = 1;
        }
        for (int i = 1; i < n; i++) {
            for (int j = 1; j < m; j++) {
                int flag = array[i-1][j];
                if(array[i][j-1] > flag){
                    flag = array[i][j-1];
                }
                int flag2 = 0;
                if(str1.charAt(i) == str2.charAt(j)) {
                     flag2 = array[i - 1][j - 1] + 1;
                }else {
                    flag2 = array[i - 1][j - 1];
                }
                if(flag2 > flag){
                    array[i][j] = flag2;
                }else {
                    array[i][j] = flag;
                }
            }
        }
        return array[n - 1][m - 1];
    }
}

2.5 一个背包有一定的承重 W,有 N 件物品,每件都有自己的价值,记录在数组 v 中,也都有自己的重量,记录在数组 w 中,每件物品只能选择要装入背包还是不装入背包,要求在不超过背包承重的前提下,选出物品的总价值最大。

dp[x][y]代表前x件物品,不超过重量y的时候的最大价值
思路:第x件物品的情况:
1.拿第x件物品,则前x-1件物品的重量不超过:y-w[x]
2.不拿第x件物品,则前x-1件物品的重量不超过y
dp[x][y] = {dp[x-1][y-w[x]] + v[x],dp[x-1][y]}中的最大值

public class literator {
    public static void main(String[] args) {
        int[] value = {0, 10, 80, 30, 35};
        int[] weight = {0, 40, 10, 20, 30};
        System.out.println(method(value, weight, 80));
    }

    public static int method(int[] value, int weight[], int aim) {
        int row = value.length;
        int[][] dp = new int[row][aim + 1];
        for (int i = 1; i < row; i++) {
            for (int j = 1; j < aim + 1; j++) {
                dp[i][j] = dp[i - 1][j];
                if (j >= weight[i]) {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
                }
            }
        }
        return dp[row-1][aim];
    }
}

2.6 给定两个字符串str1和str2,再给定三个整数ic,dc,rc,分别代表插入、删除、替换一个字符的代价,返回将str1编辑成str2的最小代价。

举例:
str1=“abc” str2=“adc” ic=5 dc=3 rc=2,从"abc"编辑到"adc"把b替换成d代价最小,为2;
str1=“abc” str2=“adc” ic=5 dc=3 rc=10,从"abc"编辑到"adc",先删除b再插入d代价最小,为8;

思路:

  • dp[i][j]代表从长度为i的字符串变换到长度为j的字符串的最下代价
  • 最小代价的四种来源:
    替换,插入,删除,不做操作这4中操作的最小值。
package com.example.demo;

public class literator {
    public static void main(String[] args) {
        String str1 = "abd";
        String str2 = "abc";
        System.out.println(method(str1, str2, 5, 6, 100));
    }
    public static int method(String str1, String str2,int ic, int dc, int rc) {
        int row = str1.length();
        int line = str2.length();
        int[][] array = new int[row + 1][line + 1];
        array[0][0] = 0;
        for (int i = 1; i < row + 1; i++) {
            array[i][0] = i * dc;
        }
        for (int i = 1; i < line + 1; i++) {
            array[0][i] = i * ic;
        }
        for (int i = 1; i < row + 1; i++) {
            for (int j = 1; j < line + 1; j++) {
                int flag = array[i-1][j] + dc;
                if(flag > array[i][j-1] + ic){
                    flag = array[i][j-1] + ic;
                }
                if(str1.charAt(i-1) != str2.charAt(j-1)) {
                    if(flag > array[i - 1][j - 1] + rc){
                        flag = array[i - 1][j - 1] + rc;
                    }
                }else {
                   if(flag > array[i - 1][j - 1]){
                       flag = array[i - 1][j - 1];
                   }
                }
                array[i][j] = flag;
            }
        }
        return array[row][line];
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值