• 动态规划常见题型


    找零钱

     题目: 给定数组arr,arr中所有的值都为整数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再你给定一个整数aim代表要找的钱数,求 换钱有多少种方法

    暴力搜索方法

    解题思路:

    概率论知识点覆盖率 
    1、用0张5元的货币,让[10, 25, 1]组成剩下的1000,最终方法数为 res1 
    2、用1张5元的货币,让[10, 25, 1]组成剩下的995,最终方法数为 res2 
    3、用2张5元的货币,让[10, 25, 1]组成剩下的990,最终方法数为 res3 
    4、用200张5元的货币,让[10, 25, 1]组成剩下的0,最终方法数为 res4 
    …. 
    最终累计额res即为结果

    不足:

    如果已经使用0张5元和1张10元的情况下,后续将求:p1(arr, 2, 990) 表示arr剩下 
    的钱为arr[2,3],即[25, 1],990表示要找的剩余钱数 
    当已经使用2张5元和0张10元的情况下,后续还是要求p1(arr, 2, 990),导致又重复之前过程


    记忆搜索方法

    1、记忆搜索是暴力搜索的优化版,加上了map,避免了重复递归过程 
    2、每计算完一个p(index, aim),都将结果放入map中,index和aim组成 
    的KEY,返回结果为Value。 
    3、要进入一个递归过程p(index, aim),线以index和aim注册的KEY在map 
    中查询是否已经存在Value,如果存在,则直接取值,如果不存在,才进行 
    递归计算


    动态规划方法

    总程序:

    1. public class Exchange {  
    2.     // 1 动态规划  
    3.     public static int countWays(int[] penny, int n, int aim) {  
    4.         int dp[][] = new int[penny.length][aim + 1];  
    5.         // aim = 0时,第一列的值都为1,表示都有一种方法  
    6.         for (int i = 0; i < n; i++) {  
    7.             dp[i][0] = 1;  
    8.         }  
    9.         // 第一行的值得方法基本能算出来,如果能aim能求余penny[0],能标记为一种方法  
    10.         for (int i = 1; i <= aim; i++) {  
    11.             if (i % penny[0] == 0) {  
    12.                 dp[0][i] = 1;  
    13.             } else {  
    14.                 dp[0][i] = 0;  
    15.             }  
    16.         }  
    17.         // dp[i][j]的方法是建立在前边的方法之上  
    18.         for (int i = 1; i < n; i++) {  
    19.             for (int j = 1; j <= aim; j++) {  
    20.                 if ((j - penny[i]) >= 0) {  
    21.                     dp[i][j] = dp[i][j - penny[i]] + dp[i - 1][j];  
    22.                 } else {  
    23.                     dp[i][j] = dp[i - 1][j];  
    24.                 }  
    25.             }  
    26.         }  
    27.         return dp[n - 1][aim];  
    28.     }  
    29.   
    30.     // 2 记忆化搜索  
    31.     public static int coins(int[] arr, int aim) {  
    32.         if (arr == null || arr.length == 0 || aim < 0) {  
    33.             return 0;  
    34.         }  
    35.         int[][] map = new int[arr.length + 1][aim + 1];  
    36.         return process(arr, 0, aim, map);  
    37.     }  
    38.   
    39.     public static int process(int[] arr, int index, int aim, int[][] map) {  
    40.         int res = 0;  
    41.         if (index == arr.length) {  
    42.             res = aim == 0 ? 1 : 0;  
    43.   
    44.         } else {  
    45.             int mapValue = 0;  
    46.             for (int i = 0; arr[index] * i <= aim; i++) {  
    47.                 mapValue = map[index + 1][aim - arr[index] * i];  
    48.                 // 每进行递归的时候,都要进行判断,如果之前已经递归过相同的索引和值,则可以直接去其map结果  
    49.                 if (mapValue != 0) {  
    50.                     res += mapValue == -1 ? 0 : mapValue;  
    51.                 } else {  
    52.                     res += process(arr, index + 1, aim - arr[index] * i, map);  
    53.                 }  
    54.             }  
    55.   
    56.         }  
    57.         // 将每个结果保存起来  
    58.         map[index][aim] = res == 0 ? -1 : res;  
    59.         return res;  
    60.   
    61.     }  
    62.   
    63.     // 3 暴力搜索 递归  
    64.     public static int coins1(int[] arr, int aim) {  
    65.         if (arr == null || arr.length == 0 || aim < 0) {  
    66.             return 0;  
    67.         }  
    68.   
    69.         return process(arr, 0, aim);  
    70.     }  
    71.   
    72.     public static int process(int[] arr, int index, int aim) {  
    73.         int res = 0;  
    74.         // 当index为最后时,判断是否满足条件,满足则算为一种方法,不满足则0方法  
    75.         if (index == arr.length) {  
    76.             res = aim == 0 ? 1 : 0;  
    77.   
    78.         } else {  
    79.             // 核心代码,利用了递归的特性  
    80.             // 最后那个值不断递归,直到结尾,前边又加1,后边又不断递归  
    81.             for (int i = 0; arr[index] * i <= aim; i++) {  
    82.   
    83.                 res += process(arr, index + 1, aim - arr[index] * i);  
    84.             }  
    85.   
    86.         }  
    87.         return res;  
    88.   
    89.     }  
    90.   
    91.     public static void main(String[] args) {  
    92.         int[] array = { 5, 10, 25, 1 };  
    93.         int n = 4;  
    94.         int aim = 1000;  
    95.         System.out.println(coins1(array, aim));  
    96.         System.out.println(coins(array, aim));  
    97.         System.out.println(countWays(array, n, aim));  
    98.     }  
    99. }  

    台阶问题

    情况一

    题目描述 有一楼梯共m级,刚开始时你在第一级,若每次只能跨上一级或二级,要走上第m级,共有多少走法? 注:规定从一级到一级有0种走法。 输入 
    输入数据首先包含一个整数n(1<=n<=100),表示测试实例的个数,然后是n行数据,每行包含一个整数m,(1<=m<=40), 
    表示楼梯的级数。 样例 
    输入 
    2 2 3 
    输出 
    对于每个测试实例,请输出不同走法的数量。

    import java.util.Scanner;
    
    public class Main {
        public static void main(String[] args) {
            Scanner sc = new Scanner(System.in);
            Main m = new Main();
            while(sc.hasNext()) {
                int num = sc.nextInt();
                for(int i=0; i<num; i++) {
                    System.out.println(m.Fan(sc.nextInt()));
                }
            }
    
        }
        public int Fan(int n) {
            if(n == 1) return 0;
            if(n == 2) return 1;
            if(n == 3) return 2;
            return Fan(n-1)+Fan(n-2);
        }
    }

    情况二

    有n级台阶,一个人每次上一级或者两级,问有多少种走完n级台阶的方法。为了防止溢出,请将结果Mod 1000000007 给定一个正整数int 
    n,请返回一个数,代表上楼的方式数。保证n小于等于100000。 
    测试样例: 1 
    返回:1

    import java.util.*;
    
    public class GoUpstairs {
        public int countWays(int n) {
            int f1 = 1;
            int f2 = 2;
            int temp = 0;
    
            for (int i = 3; i <= n; i++){
                // 防止数组越界非法访问等情况
                temp = (f1 + f2) % 1000000007;
                f1 = f2;
                f2 = temp;
            }
            return f2;
        }
    }

    背包问题

    一个背包有一定的承重cap,有N件物品,每件都有自己的价值,记录在数组v中,也都有自己的重量,记录在数组w中,每件物品只能选择要装入背包还是不装入背包,要求在不超过背包承重的前提下,选出物品的总价值最大。 
    给定物品的重量w价值v及物品数n和承重cap。请返回最大总价值。 
    测试样例: [1,2,3],[1,2,3],3,6 
    返回:6

    import java.util.*;
    
    public class Backpack {
        public int maxValue(int[] w, int[] v, int n, int cap) {
            int[][] dp = new int[n+1][cap+1];
            for (int i = 0; i <= cap; i++){
                dp[0][i] = 0;
            }
            for (int i = 0; i <= n; i++){
                dp[i][0] = 0;
            }
            for (int i = 1; i <= n; i++){
                for (int j = 1; j <= cap; j++){
                    // 不选择 i 情况的价值
                    dp[i][j] = dp[i-1][j];
                    // 选择 i 的情况, 当满足条件时,比较选择与不选择i的最大值
                    // w[i-1] 之所以会减一,是因为 i == 1时,表示第一个物品,而w的第一个物品为w[0]。v[i-1]也是如此
                    if (j - w[i-1] >= 0){
                        dp[i][j] = Math.max(dp[i][j], dp[i-1][j-w[i-1]] + v[i-1]);
                    }
                }
            }
            return dp[n][cap];
        }
    }

    矩阵最小路径和练习题

    有一个矩阵map,它每个格子有一个权值。从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。 
    给定一个矩阵map及它的行数n和列数m,请返回最小路径和。保证行列数均小于等于100. 
    测试样例: 
    [[1,2,3],[1,1,1]],2,3 
    返回:4

    import java.util.*;
    
    public class MinimumPath {
        public int getMin(int[][] map, int n, int m) {
            int[][] dp = new int[n][m];
            // dp的第一个元素为map的第一个元素
            dp[0][0] = map[0][0];
            for (int i = 1; i < m; i++){
                dp[0][i] = dp[0][i-1] + map[0][i];
            }
            for (int i = 1; i < n; i++){
                dp[i][0] = dp[i-1][0] + map[i][0];
            }
            for (int i = 1; i < n; i++){
                for (int j = 1; j < m; j++){
                    // 求向右或向下的最小值并加上到达位置的map值
                    dp[i][j] = Math.min(dp[i-1][j], dp[i][j-1]) + map[i][j];
                }
            }    
            return dp[n-1][m-1];
        }
    }

    LIS练习题

    这是一个经典的LIS(即最长上升子序列)问题,请设计一个尽量优的解法求出序列的最长上升子序列的长度。 
    给定一个序列A及它的长度n(长度小于等于500),请返回LIS的长度。 
    测试样例: [1,4,2,5,3],5 
    返回:3

    import java.util.*;
    
    public class LongestIncreasingSubsequence {
        public int getLIS(int[] A, int n) {
            int[] dp = new int[n];
            // 第一个数长度为 1
            dp[0] = 1;
            for (int i = 1; i < n; i++){
                // 求 dp[0] ~ dp[i-1] 的最长子序列 且 A[j] < A[i]
                int maxLength = 0;
                for (int j = 0; j < i; j++){
                    if (A[j] < A[i] && maxLength < dp[j]){
                        maxLength = dp[j];
                    }
                }
                // 加上本身
                dp[i] = maxLength + 1;
            }
            // 遍历dp,求最大长度子序列
            int maxLength = 0;
            for (int i = 0; i < n; i++){
                if (maxLength < dp[i]){
                    maxLength = dp[i];
                }
            }
            return maxLength;
        }
    }

    LCS练习题

    给定两个字符串A和B,返回两个字符串的最长公共子序列的长度。例如,A=”1A2C3D4B56”,B=”B1D23CA45B6A”,”123456”或者”12C4B6”都是最长公共子序列。 
    给定两个字符串A和B,同时给定两个串的长度n和m,请返回最长公共子序列的长度。保证两串长度均小于等于300。 
    测试样例: 
    “1A2C3D4B56”,10,”B1D23CA45B6A”,12 
    返回:6

    import java.util.*;
    
    public class LCS {
        public int findLCS(String A, int n, String B, int m) {
            char[] a = A.toCharArray();
            char[] b = B.toCharArray();
            int[][] dp = new int[n+1][m+1];
            // 为了避免越界,都从数组下标 1 开始
            for (int i = 1; i <= n; i++){
                for (int j = 1; j <= m; j++){
                    // 如果两个字符相等,则在之前的基础上 +1
                    if (a[i-1] == b[j-1]){
                        dp[i][j] = dp[i-1][j-1] + 1;
                    }
                    // 求上边或左边的最大值
                    else{
                        dp[i][j] = Math.max(dp[i-1][j], dp[i][j-1]);
                    }
                }
            }
            return dp[n][m];
        }
    }

    最优编辑练习题

    对于两个字符串A和B,我们需要进行插入、删除和修改操作将A串变为B串,定义c0,c1,c2分别为三种操作的代价,请设计一个高效算法,求出将A串变为B串所需要的最少代价。 
    给定两个字符串A和B,及它们的长度和三种操作代价,请返回将A串变为B串所需要的最小代价。保证两串长度均小于等于300,且三种代价值均小于等于100。 
    测试样例: “abc”,3,”adc”,3,5,3,100 00 
    返回:8

    import java.util.*;
    
    public class MinCost {
        // c0 插入, c1 删除, c2 替换
        public int findMinCost(String A, int n, String B, int m, int c0, int c1, int c2) {
            // a编辑成b
            char[] a = A.toCharArray();
            char[] b = B.toCharArray();
            int[][] dp =new int[n+1][m+1];
            // 第一个字符为 ‘’;
            for (int i = 1; i <= m; i++){
                // 行 为 插入
                dp[0][i] = c0 * i;
            }
            for (int i = 1; i <= n; i++){
                // 列 为 删除
                dp[i][0] = c1 * i;
            }
    
            for (int i = 1; i <= n; i++){
                // 到达 dp[i][j] 一共有四种情况
                for (int j = 1; j <= m; j++){
                    // 取最大值,以求出两数比较的最小值
                    int temp1 = Integer.MAX_VALUE;
                    int temp2 = Integer.MAX_VALUE;
                    // 从dp[i-1][j-1] 到 dp[i][j] 的情况,一共两种,相同时为0代价,不同时为 替换 代价;
                    if (a[i-1] == b[j-1]){
                        temp1 = dp[i-1][j-1];
                    }
                    else{
                        // 不同时,替换
                        temp2 = dp[i-1][j-1] + c2;
                    }
                    // 从 dp[i-1][j] 到 dp[i][j] 的情况, 因为dp[i-1][j] 已经满足了条件b[j-1](从1开始),可通过删除下一位c1元素则可满足dp[i][j]
                    int temp3 = dp[i-1][j] + c1;
                    // 从 dp[i][j-1] 到 dp[i][j] 的情况, 因为dp[i-1][j] 还差一个元素就满足b[j-1](从1开始),可通过插入下一位c0元素则可满足dp[i][j]
                    int temp4 = dp[i][j-1] + c0;
                    // 从四种情况中选出代价最小的情况
                    dp[i][j] = Math.min(Math.min(temp1, temp2), Math.min(temp3, temp4));
    
                }
            }
            return dp[n][m];
        }
    }
  • 相关阅读:
    PHP常用代码大全(新手入门必备)
    一代大商孟洛川的经商之道
    Photoshop调出田园照片唯美手绘油画效果
    Photoshop调出外景婚片蓝色小清新艺术效果
    photoshop快速把新照片制作成老照片教学
    Photoshop调出清晰的阴雨天气山水风景照
    PS调出清新风格社区街拍照片
    PS调出韩式米黄色室内婚纱照片
    PS调出唯美紫蓝色天空背景女生照片
    PS快速调出天蓝色清新外景
  • 原文地址:https://www.cnblogs.com/wwjldm/p/7240595.html
Copyright © 2020-2023  润新知