算法——动态规划算法

动态规划的核心思路

动态规划的解题核心主要分为两步:

第一步:定义问题

有的问题过于抽象,或者过于啰嗦干扰我们解题的思路,我们要做的就是将题干中的问题转化成一系列同类问题的某个解的情况,比如说:

题目:求一个数列中最大连续子序列的和。

我们要将这个原问题转化为:

定义问题:Fk是第k项前的最大序列和,求F1~FN中最大值。

通过换一种表述方式,我们清晰的发现了解决问题的思路,如何求出F1~FN中的最大值是解决原问题的关键部分。这样的定义给出了一种类似通解的思路,把一个原来毫无头绪的问题转换成了可以求解的问题。

第二步:定义方程

在进行了问题的定义后,自然而然的想到去求解F1~FN中最大值。这也是问题定义的作用,让我们把一个总体的问题转化成一系列问题,而第二步:定义方程的定义则告诉我们如何去求解一个问题,对于上述已经转换成一系列问题我们要关注的点就在于:如何能够用前一项或者前几项的信息得到下一项,这种从最优子状态转换为下一个最优状态的思路就是动态规划的核心。 

对于上面的例子题目来说,状态转移方程的定义应该是:

Fk=max{Fk-1+Ak,Ak} 
Fk是前k项的和,Ak是第k项的值

仔细思考一番,我们能够得到这样的结论,对于前k个项的最大子序列和是前k-1项的最大子序列和Fk与第k项的和、或者第k项两者中较大的。如果大家还是不能理解这个原理建议用演算纸自己计算一番,这里就不过多赘述了。这种状态转移的思路就是DP的核心。

何时使用动态规划算法

看过了如何去使用动态规划,那么随之而来的问题就在于:既然动态规划不是某个固定的算法,而是一种策略思路,那么什么时候应该去使用什么时候不能用呢?答案很简短:

对于一个可拆分问题中存在可以由前若干项计算当前项的问题可以由动态规划计算。 

例题1: 数塔取数问题

一个高度为N的由正整数组成的三角形,从上走到下,求经过的数字和的最大值。 
每次只能走到下一层相邻的数上,例如从第3层的6向下走,只能走到第4层的2或9上。

该三角形第n层有n个数字,例如:

第一层有一个数字:5

第二层有两个数字:8 4

第三层有三个数字:3 6 9

第四层有四个数字:7 2 9 5

最优方案是:5 + 8 + 6 + 9 = 28

注意:上面应该是排列成一个三角形的样子不是竖向对应的,排版问题没有显示成三角形。

问题定义: Fi,j是第i行j列项最大取数和,求第n行Fn,m(0 < m < n)中最大值。

方程定义:Fi,j = max{Fi-1,j-1,Fi-1,j}+Ai,jt

package com.hust0328;

import java.util.Scanner;

/**
 * Created by huststl on 2018/3/28 14:24
 * 动态规划题01
 *
 */
public class Dp01 {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);

        int n = scan.nextInt();
        long max = 0;
        int[][] dp = new int[n][n];
        dp[0][0] = scan.nextInt();

        for(int i=1;i<n;i++){
            for(int j=0;j<=i;j++){
                int num = scan.nextInt();
                if(j==0){
                    dp[i][j] = dp[i-1][j] + num;
                }else {
                    dp[i][j] = Math.max(dp[i-1][j-1],dp[i - 1][j])+num;
                }
                max = Math.max(dp[i][j],max);
            }
        }
        System.out.println(max);
    }
}

2、编辑距离

编辑距离,又称Levenshtein距离(也叫做Edit Distance),是指两个字串之间,由一个转成另一个所需的最少编辑操作次数。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符。 
例如将kitten一字转成sitting: 
sitten (k->s) 
sittin (e->i) 
sitting (->g) 
所以kitten和sitting的编辑距离是3。俄罗斯科学家Vladimir Levenshtein在1965年提出这个概念。 
给出两个字符串a,b,求a和b的编辑距离。

状态定义:Fi,j表示第一个字符串的前i个字母和第二个字符串的前j个字母需要编辑的次数,求Fn,m,n和m分别是两个字符串的长度。

状态转移方程: 
当Fi,j-1=Fi-1,j时,Fi,j=Fi,j-1; 
当Fi,j-1!=Fi-1,j时,Fi,j=min{Fi-1,j-1,Fi,j-1,Fi-1,j}+1.

package com.hust0328;

import java.util.Scanner;

/**
 * Created by huststl on 2018/3/28 14:44
 * 动态规划02
 */
public class dp02 {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String aStr = scan.nextLine();
        String bStr = scan.nextLine();
        int aLen = aStr.length();
        int bLen = bStr.length();
        int[][] dp = new int[aLen+1][bLen+1];
        for(int i=0;i<aLen+1;i++){
            dp[i][0] = i;
        }
        for(int i=0;i<bLen+1;i++){
            dp[0][i] = i;
        }
        for(int i=1;i<aLen+1;i++){
            for(int j=1;j<bLen+1;j++){
                if(aStr.charAt(i-1) == bStr.charAt(j-1)){
                    dp[i][j] = dp[i-1][j-1];
                }else {
                    dp[i][j] = Math.min(dp[i - 1][j - 1], Math.min(dp[i - 1][j], dp[i][j - 1])) + 1;
                }
            }
        }
        System.out.println(dp[aLen][bLen]);
    }
}

3、矩阵取数问题 

 一个N*N矩阵中有不同的正整数,经过这个格子,就能获得相应价值的奖励,从左上走到右下,只能向下向右走,求能够获得的最大价值。例如:3 * 3的方格。

1 3 3 
2 1 3 
2 2 1

能够获得的最大价值为:11。

package com.hust0328;

import java.util.Scanner;

/**
 * Created by huststl on 2018/3/28 14:55
 * 矩阵取数问题
 */
public class Dp03 {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int[] dp = new int[n+1];
        int[] readMax = new int[n+1];

        for(int i=0;i<n;i++){
            for(int k=1;k<n+1;k++){
                readMax[k] = scan.nextInt();
            }
            for(int j=1;j<n+1;j++){
                dp[j] = Math.max(dp[j],dp[j-1])+readMax[j];
            }
        }
        System.out.println(dp[n]);
    }
}

4、背包问题

在N件物品取出若干件放在容量为W的背包里,每件物品的体积为W1,W2……Wn(Wi为整数),与之相对应的价值为P1,P2……Pn(Pi为整数)。求背包能够容纳的最大价值。 

package com.hust0328;

import java.util.Scanner;

/**
 * Created by huststl on 2018/3/28 15:06
 * 背包问题
 */
public class Dp04 {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int v = scan.nextInt();
        int[] dp = new int[v+1];
        int[] price = new int[n+1];
        int[] weight = new int[n+1];
        long max = 0;
        for(int i=1;i<n+1;i++){
            weight[i] = scan.nextInt();
            price[i] = scan.nextInt();
        }
        for(int i = 1;i<n+1;i++){
            for(int j= v;j>0;j--){
                if(j-weight[i] >= 0){
                    dp[j] = Math.max(dp[j],dp[j-weight[i]]+price[i]);
                }else {
                    dp[j] = dp[i];
                }
            }

        }
        for(int i=0;i<v+1;i++){
            max = max > dp[i]?max:dp[i];
        }
        System.out.println(max);
    }
}

5、最长递增子序列

给出长度为N的数组,找出这个数组的最长递增子序列。 
(递增子序列是指,子序列的元素是递增的) 
例如:5 1 6 8 2 4 5 10,最长递增子序列是1 2 4 5 10。 

package com.hust0328;

import java.util.Scanner;

/**
 * Created by huststl on 2018/3/28 15:18
 * 最长递增子序列
 */
public class Dp05 {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        int[] num = new int[n];
        for(int i=0;i<n;i++){
            num[i] = scan.nextInt();
        }
        double[] dou = new double[n+1];
        dou[0] = Integer.MIN_VALUE;
        dou[1] = num[0];
        int Len = 1;
        int p,r,m;
        for(int i=1;i<n;i++){
            p = 0;
            r = Len;
            while (p<=r){
                m = (p+r)/2;
                if(dou[m] < num[i]){
                    p = m+1;
                }else {
                    r = m-1;
                }
            }
            dou[p] = num[i];
            if(p>Len){
                Len++;
            }
        }
        System.out.println(Len);
     }
}

6、最大子段和

N个整数组成的序列a[1],a[2],a[3],…,a[n],求该序列如a[i]+a[i+1]+…+a[j]的连续子段和的最大值。 
当所给的整数均为负数时和为0。 
例如:-2,11,-4,13,-5,-2,和最大的子段为:11,-4,13。和为20。 

package com.hust0328;
/**
 * Created by huststl on 2018/3/28 15:33
 * 最大子段和
 */
public class Dp06 {
   public static int maxSubSum1(int []a){
        int maxSum=0; int nowSum=0;
        for(int i=0;i<a.length;i++){
            nowSum=nowSum+a[i];
            if(nowSum>maxSum){//更新最大子段和
                maxSum=nowSum;
            }
            if(nowSum<0){//当当前累加和为负数时舍弃,重置为0
                nowSum=0;
            }
        }
        return maxSum;
    }
    public static void main(String[] args) {
        int a[]={-2,11,-4,13,-5,-2};
        System.out.println(maxSubSum1(a));


    }
}

7、最长公共子序列Lcs

给出两个字符串A B,求A与B的最长公共子序列(子序列不要求是连续的)。 
比如两个串为:

abcicba 
abdkscab

ab是两个串的子序列,abc也是,abca也是,其中abca是这两个字符串最长的子序列。

package com.hust0328;

import java.util.Scanner;

/**
 * Created by huststl on 2018/3/28 15:55
 * 最长公共子序列Lcs
 */
public class Dp07 {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        String aStr = scan.nextLine();
        String bStr = scan.nextLine();
        int aLen = aStr.length();
        int bLen = bStr.length();
        int[][] dp = new int[aLen+1][bLen+1];
        for(int i=1;i<aLen+1;i++){
            for(int j=1;j<bLen+1;j++){
                if(dp[i - 1][j] == dp[i][j - 1] && aStr.charAt(i - 1) == bStr.charAt(j - 1)
                        && dp[i - 1][j] == dp[i - 1][j - 1]){
                    dp[i][j] = dp[i-1][j]+1;
                }else {
                    dp[i][j] = Math.max(dp[i-1][j],dp[i][j-1]);
                }
            }
        }
        int max = dp[aLen][bLen];
        StringBuilder sb = new StringBuilder(128);
        while (max>0){
            if(dp[aLen-1][bLen] == dp[aLen][bLen-1] && dp[aLen - 1][bLen] + 1 == dp[aLen][bLen]){
                sb.append(aStr.charAt(aLen-1));
                max--;
                aLen--;
                bLen--;
            }else {
                if(dp[aLen][bLen-1] == dp[aLen][bLen]){
                    bLen--;
                }else {
                    aLen--;
                }
            }
            System.out.println(sb.reverse().toString());
        }
    }
}

8、正整数分组

将一堆正整数分为2组,要求2组的和相差最小。 
例如:1 2 3 4 5,将1 2 4分为1组,3 5分为1组,两组和相差1,是所有方案中相差最少的。 

package com.hust0328;

import java.util.Scanner;

/**
 * Created by huststl on 2018/3/28 16:18
 * 正整数分组
 */
public class dp08 {
    public static void main(String[] args) {
        Scanner scan = new Scanner(System.in);
        int n = scan.nextInt();
        long sum = 0,max = 0;
        int[] nums = new int[n];
        for(int i=0;i < n;i++){
            nums[i]= scan.nextInt();
            sum += nums[i];
        }
        int[] dp = new int[(int)(sum/2+1)];
        for(int i=0;i<n;i++){
            for(int j = (int) (sum / 2); j > 0; j--){
                if(j >=nums[i]){
                    dp[j] = Math.max(dp[j],dp[j-nums[i]]+nums[i]);
                }
            }

        }
        for(int i=1;i<sum/2+1;i++){
            max = max >dp[i]?max:dp[i];
        }
        System.out.println(Math.abs((sum-max)-max));

    }
}

 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值