• 动态规划


    什么是动态规划方法?

    1. 其本质是利用申请的空间来记录每一个暴力搜索的计算结果,下次要使用结果的时候直接使用,而不在进行重复的递归过程。
    2. 动态规划规定每一种递归状态的计算顺序,依次进行计算。(而记忆搜索方法不规定计算顺序)

    面试中遇到的暴力递归题目可以优化成动态规划方法的大体过程:

    1. 实现暴力递归方法。
    2. 在暴力搜索方法的函数中看看那些参数可以代表递归过程。
    3. 找到代表递归过程的参数之后,记忆化搜索的方法非常容易实现。
    4. 通过分析记忆化搜索方法的依赖路径,进而实现动态规划。
    5. 根据记忆化搜索方法改出动态规划方法,进而看看是否能化简,如果能化简,还能实现时间复杂度更低的动态规划方法。

    动态规划方法的关键点:

    1. 最优子结构性质。
    2. 无后效性。指的是某状态下决策的受益,只与状态和决策相关,与到达该状态的方式无关。
    3. 子问题的重叠性。解决在暴力搜索过程中冗余计算的问题,这是动态规划算法的根本目的。

    PS:之所以认为动态规划比较难,是因为对于暴力搜索不了解,导致对其优化过程不了解,找不出状态转移方程,画不出状态转移表,最终也无法写出动态规划。

    此外:对于经典的动态规划问题如:硬币找零问题、0-1背包问题、LCS问题,直接记住动态规划的方法是什么样的,而不是从暴力过程快慢慢整理出来,因为这些经典问题是教材级别的问题,面试官希望面试者能直接说动态规划的过程。

    1.硬币找零问题

    题干:

    有数组penny,penny中所有的值都为正数且不重复。每个值代表一种面值的货币,每种面值的货币可以使用任意张,再给定一个整数aim(小于等于1000)代表要找的钱数,求换钱有多少种方法。

    给定数组penny及它的大小(小于等于50),同时给定一个整数aim,请返回有多少种方法可以凑成aim。

    解答:申请一个数组dp[n][m],其中n代表货币的面值种类,m代表要找的钱数,则dp[i][j] 为使用一共i种货币,要找零钱为j的情况下换钱的方法,其状态转移方程为:dp[i][j] = dp[i - 1][j] + dp[i][j - penny[i],即是使用第i种货币和不使用第i种货币的情况总和。

     1 int countWays(vector<int> penny, int n, int aim) {
     2         // write code here
     3         int dp[50][1001];
     4         for(int i = 0; i < n; i++){
     5             dp[i][0] = 1;
     6         }
     7         for(int j = 0; j <= aim; j++){
     8             if(j % penny[0] == 0)
     9                 dp[0][j] = 1;
    10             else
    11                 dp[0][j] = 0;
    12         }
    13         
    14         for(int i = 1; i < n; i++){
    15             for(int j = 1; j <= aim; j++)
    16                 if(j >= penny[i])
    17                     dp[i][j] = dp[i - 1][j] + dp[i][j - penny[i]];
    18                 else
    19                     dp[i][j] = dp[i - 1][j];
    20         }
    21         
    22         return dp[n - 1][aim];
    23     }

    2.矩阵最小路径和

    题干:

    有一个矩阵map,它每个格子有一个权值。从左上角的格子开始每次只能向右或者向下走,最后到达右下角的位置,路径上所有的数字累加起来就是路径和,返回所有的路径中最小的路径和。

    给定一个矩阵map及它的行数n和列数m,请返回最小路径和。保证行列数均小于等于100.

     1 int getMin(vector<vector<int> > map, int n, int m) {
     2         // write code here
     3         //dp[i][j] 表示从左上角走到坐标为i,j的位置所经过的最短路径
     4         int dp[100][100];
     5         dp[0][0] = map[0][0];
     6         for(int i = 1; i < m; i++)
     7             dp[0][i] = dp[0][i - 1] + map[0][i];
     8         for(int i = 1; i < n; i++)
     9             dp[i][0] = dp[i - 1][0] + map[i][0];
    10          
    11         for(int i = 1; i < n; i++){
    12             for(int j = 1; j < m; j++){
    13                 dp[i][j] = min(dp[i - 1][j],dp[i][j - 1]) + map[i][j];
    14             }
    15         }
    16          
    17         return dp[n - 1][m - 1];
    18     }
    19      
    20     /*
    21     int min(int a,int b){
    22         return a < b ? a : b;
    23     }
    24     */

    3.LIS

    题干:

    这是一个经典的LIS(即最长上升子序列)问题,请设计一个尽量优的解法求出序列的最长上升子序列的长度。

    给定一个序列A及它的长度n(长度小于等于500),请返回LIS的长度。

     1 int getLIS(vector<int> A, int n) {
     2         // write code here
     3         //dp[i]表示为以A[i]结尾的最长上升子序列的长度,最后再找出dp[i]的最大值
     4         //dp[i] = dp[j] + 1(j >= 0 && j < i),且dp[j]为max 
     5         int dp[500] = {1};
     6          
     7         for(int i = 1; i < n; i++){
     8             //找出dp[j](j < i && j >= 0)中的最大值且满足A[j] < A[i](满足上升特性);
     9             int curMax = 0;
    10             for(int j = 0; j < i; j++){
    11                 if(dp[j] > curMax  && A[j] < A[i])
    12                     curMax = dp[j];
    13             }
    14             dp[i] = curMax + 1;
    15              
    16         }
    17         
    18         //找出dp数组中的最大值
    19         int max = 0;
    20         for(int i = 0; i < n; i++){
    21             if(dp[i] > max)
    22                 max = dp[i];
    23         }
    24          
    25         return max;
    26     }

    4.LCS(最长公共子序列)

    题干:

    给定两个字符串A和B,返回两个字符串的最长公共子序列的长度。例如,A="1A2C3D4B56”,B="B1D23CA45B6A”,”123456"或者"12C4B6"都是最长公共子序列。

    给定两个字符串AB,同时给定两个串的长度nm,请返回最长公共子序列的长度。保证两串长度均小于等于300。

     1 int findLCS(string A, int n, string B, int m) {
     2         // write code here
     3         //dp[i][j]表示str1[0,1,...,i]和str2[0,1,...,j]的最长公共子序列长度
     4         int dp[300][300] = {0};
     5         // 注意它们的初始化
     6         for(int j = 0; j < m; j++){
     7             if(A[0] == B[j]){
     8                 dp[0][j] = 1;
     9                 while(j++ < m)
    10                     dp[0][j] = 1;
    11                 break;
    12             }
    13         }
    14          
    15         for(int i = 0; i < n; i++){
    16             if(A[i] == B[0]){
    17                 dp[i][0] = 1;
    18                 while(i++ < n)
    19                     dp[i][0] = 1;
    20                 break;
    21             }
    22         }
    23          
    24         for(int i = 1; i < n; i++){
    25             for(int j = 1; j < m; j++){
    26                 if(A[i] == B[j]){
    27                     dp[i][j] = dp[i - 1][j - 1] + 1;
    28                 }
    29                 else
    30                     dp[i][j] = max(dp[i - 1][j],dp[i][j - 1]);
    31             }
    32         }
    33          
    34         return dp[n - 1][m - 1];
    35          
    36     }

    或者这样,初始化上更简单

     1 int findLCS(string A, int n, string B, int m) {
     2         // write code here
     3         //dp[i][j]表示str1[0,1,...,i]和str2[0,1,...,j]的最长公共子序列长度
     4         int dp[301][301] = {0};
     5         // 注意它们的初始化,这样更简单
     6         for(int j = 0; j < m; j++){
     7                dp[0][j] = 0;
     8         }
     9          
    10         for(int i = 0; i < n; i++){
    11                 dp[i][0] = 0;
    12         }
    13          
    14         for(int i = 1; i <= n; i++){
    15             for(int j = 1; j <= m; j++){
    16                 if(A[i] == B[j]){
    17                     dp[i][j] = dp[i - 1][j - 1] + 1;
    18                 }
    19                 else
    20                     dp[i][j] = max(dp[i - 1][j],dp[i][j - 1]);
    21             }
    22         }
    23          
    24         return dp[n - 1][m - 1];
    25          
    26     }

    5.0-1背包问题

    题干:

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

    给定物品的重量w价值v及物品数n和承重cap。请返回最大总价值。

     1 int maxValue(vector<int> w, vector<int> v, int n, int cap) {
     2         // write code here
     3         //dp[i][j]表示放前i件物品,背包容量为j时的最大价值
     4          
     5         int dp[n][cap + 1];  //背包容量可以为cap
     6         for(int i = 0; i < n; i++)
     7             dp[i][0] = 0;
     8         for(int j = 1; j <= cap; j++){
     9             if(j >= w[0])
    10                 dp[0][j] = v[0];
    11             else
    12                 dp[0][j] = 0;
    13         }
    14          
    15         for(int i = 1; i < n; i++){
    16             for(int j = 1; j <= cap; j++){  //注意下标
    17                 if(j < w[i])
    18                     dp[i][j] = dp[i - 1][j];
    19                 else
    20                     dp[i][j] = max(dp[i - 1][j],dp[i - 1][j - w[i]] + v[i]);
    21             }
    22         }
    23          
    24          
    25         return dp[n - 1][cap];

    6.最优编辑

    题干:

    对于两个字符串A和B,我们需要进行插入、删除和修改操作将A串变为B串,定义c0,c1,c2分别为三种操作的代价,请设计一个高效算法,求出将A串变为B串所需要的最少代价。

    给定两个字符串AB,及它们的长度和三种操作代价,请返回将A串变为B串所需要的最小代价。保证两串长度均小于等于300,且三种代价值均小于等于100。

     1 int findMinCost(string A, int n, string B, int m, int c0, int c1, int c2) {
     2         // write code here
     3         //dp[i][j]代表将str1(0,1,...,i-1)变为str2(0,1,2,...,j-1)的最小代价,注意有空字符串
     4         int dp[n + 1][m + 1];
     5         dp[0][0] = 0;
     6         for(int i = 1; i <= n; i++)  //表示第一列
     7             dp[i][0] = c1 * i;
     8         for(int j = 1; j <= m; j++)  //表示第一行
     9             dp[0][j] = c0 * j;
    10         for(int i = 1; i <= n; i++){
    11             for(int j = 1; j <= m; j++){
    12                 if(A[i - 1] == B[j - 1])
    13                     dp[i][j] = dp[i - 1][j - 1];
    14                 else {
    15                     int min1 = min(dp[i - 1][j] + c1,dp[i][j - 1] + c0);
    16                     dp[i][j] = min(min1,dp[i - 1][j - 1] + c2);  
    17                 }
    18             }
    19         }
    20          
    21         return dp[n][m];
    22          
    23     }
    只为训练自己,时刻锤炼一个程序员最基本的技能!
  • 相关阅读:
    git-guide
    jenkinsfile 庖丁解牛
    pipline预发的'拉取代码'的stage
    jenkinsfile的credentialsId
    jenkins配置ssh插件
    Rancher
    福至心灵篇-python
    生成器和迭代器思维导图(待补充)
    怎么样才能使内心平静?
    Java中的语法糖
  • 原文地址:https://www.cnblogs.com/coding-wtf/p/5800574.html
Copyright © 2020-2023  润新知