动态规划算法典型例题

1. 动态规划之选数问题

关于动态规划算法的介绍可以参考我上一篇博客:https://blog.csdn.net/can_chen/article/details/105888291

题目要求:

假设给定一串数字{1, 2, 4, 1, 7, 8, 3},我们要从中选择若干个数,使最后的和达到最大。选择的规则是,不能选相邻的数字。比如:如果我们选了第一个数字1,那么我们就不能选2,如果我们选择了数字4,那么我们就不能选择与它相邻的2和1。

动态规划的思想:将整个问题划分成一个个子问题,也就是说要求整个数列的最大和,可以先求出前面若干个数的和,一直划分到求出只有一个数的最大和(即本身),而每个子问题的解对于后面的结果都是有用的,这就是用到了动态规划的思想

思路:

对于每个数,都有选和不选两种状态,如果选了这个数,那么就不能选他相邻的那个数,那么最大和就等于本身加上他相邻的那位数之前那些数的最大和,如果不选这个数,那么便可以选它相邻的那个数,那么最大和就等于这个数之前的那些数的最大和。最终的结果就是取这两种选择的最大值

这里就可以使用递归,即Math.max(dp(arr, x-2)+arr[x], dp(arr, x-1));

递归的出口:

  • 如果只有一个数,那么最大和就是本身
  • 如果只有两个数,那么最大和就是这两个数中的最大值

递归解法:(效率低)

/**
* 动态规划之选数问题:递归解法
* @param arr:将给定的一串数字放在数组中
* @param x:数组下标,也就是求出数组第一个元素到这个下标之间的最大和
* @return
*/
public static int dp(int[] arr,int x){
    if(x==0){
        return arr[0];
    }
    if(x==1){
        return Math.max(arr[0], arr[1]);
    }
    return Math.max(dp(arr, x-2)+arr[x], dp(arr, x-1));
}

非递归解法:

由于递归会重复计算相同的值,所以我们这里使用非递归解法,也就是创建一个数组来保存计算过的值,由此提高效率。例如temp[0]用来保存数组中只有一个数的最大和,temp[1]用来保存数组中只有两个数的最大和;temp[arr.length()-1]用来保存整个数组的最大和

/**
* 动态规划之选数问题:非递归解法
* @return
*/
public static int dp(int[] arr){
    int[] temp=new int[arr.length];
    temp[0]=arr[0];
    temp[1]=Math.max(arr[0], arr[1]);
    for(int i=2;i<arr.length;i++){
        temp[i]=Math.max(temp[i-2]+arr[i],temp[i-1]);
    }
    return temp[arr.length-1];
}

2. 动态规划之最长公共子序列

最长公共子序列问题:longest common subsequence,也叫LCS问题,是动态规划算法的经典例题;解题步骤类似于背包问题,通过创建一个二维数组,并逐步进行填充,通过填表,总结出此问题的公式!

题目描述:

给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。

一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的公共子序列是这两个字符串所共同拥有的子序列。

若这两个字符串没有公共子序列,则返回 0。

/**
* 动态规划之最长公共子序列
* @return
*/
public static int longestCommonSubsequence(String text1, String text2) {
    int len1=text1.length();
    int len2=text2.length();
    char[] ch1=text1.toCharArray();
    char[] ch2=text2.toCharArray();

    //动态规划算法创建的二维数组一般都得多增一行一列,这是为了防止数组越界异常
    int[][] p=new int[len1+1][len2+1];

    //填表,第一行和第一列都为0,所以填表从下标为1的行和下标为1的列开始填
    for(int i=1;i<p.length;i++){
        for(int j=1;j<p[0].length;j++){
            if(ch1[i-1]==ch2[j-1]){
                p[i][j]=p[i-1][j-1]+1;
            }else{
                p[i][j]=Math.max(p[i-1][j], p[i][j-1]);
            }
        }
    }	
    return p[len1][len2];
}

3. 动态规划之最长公共子串

最长公共子串与最长公共序列是易混的概念,公共子串和公共子序列不同,公共子序列不要求连续,但是公共子串必须是连续的。例如:String s1=“helloworld”;String s2=“loop”;则s1和s2的最长公共子序列是“loo",但是最长公共子串是"lo"

最长公共子串问题也是动态规划算法的经典例题,解题思路和最长公共子序列类似,和LCS问题唯一不同的地方在于当c1[i] != c2[j]时,p[i] [j]就直接等于0了,因为子串必须连续,且p[i] [j]表示的是以c1[i],c2[j]截尾的公共子串的长度。这个和LCS问题还有一点不同的就是,需要设置一个max,每一步都更新得到最长公共子串的长度。

题目描述:

最大公共子串长度问题就是:求两个串的所有子串中能够匹配上的最大长度是多少。
比如:“abcdkkk” 和 “baabcdadabc”,可以找到的最长的公共子串是"abcd",所以最大公共子串长度为4。

/**
* 动态规划之最长公共子串
* @return
*/
public static int f(String s1, String s2) {
    char[] c1 = s1.toCharArray();
    char[] c2 = s2.toCharArray();

    int[][] p= new int[c1.length + 1][c2.length + 1];

    int max = 0;
    for (int i = 1; i < p.length; i++) {
        for (int j = 1; j < p[i].length; j++) {
            if (c1[i - 1] == c2[j -1]) {
                p[i][j] = p[i - 1][j - 1] + 1;
                if (p[i][j] > max)
                    max = p[i][j];
            }
        }
    }
    return max;
}

总结:动态规划算法常见的两种解题思路:

  • 一是使用递归,动态规划算法使用的递归与普通递归略有区别,比如选数问题使用的递归,各个子问题之间是有联系的,这就是动态规划的递归;而分治算法也常使用递归,例如二分查找,各个子问题是相互独立的。
  • 二是通过创建二维数组(二维数组通常要多创建一行一列防止代码书写过程出现越界异常),然后逐步填充二维数组,并得到求解问题的递推公式!

猜你喜欢

转载自blog.csdn.net/can_chen/article/details/105888692