动态规划三道例题对比学习(0-1背包/最长公共子序列/石子合并)附流程图+备忘录图

这里是申申如也,一个把编程当成爱好的程序员,期待与你一起进步!

动态规划

分析

最优子结构:不管子问题多小,此时都是最优解。当这些子问题合并就会得到总问题。

重叠子问题:我们将各种最优结构记录在表中,因为之后的问题可能都会用到(看下文慢慢理解)

备忘录方法:即将重叠的问题记录在表中。

解题流程

  1. 将原问题分解为子问题,并保存子问题的解
  2. 确定状态:用动态规划解题时,我们往往将和子问题相关的各个变量的一组取值,称之为一个“状 态”。
  3. 确定初始值.
  4. 确定状态转移方程

然后看题

0-1背包问题

有n个物品,它们有各自的体积和价值,现有给定容量的背包,如何让背包里装入的物品具有最大的价值总和?

子问题

从背包容量为0开始,从只装一个物品开始,只要是最能装的(即重量小,价值大)。就是一个最优的子问题,然后看能不能合并,很明显这是可以合并的,我可以从小背包少物品开始一步步到大背包多物品。

确定状态

只有两种,装还是不装。

装了之后包里的物品价值总和是不是最大,不装之前容量是如何的(能不能装进去)都决定我们能不能装这个物品。

那我们如何判断呢?

当然是价值大重量小的优先。

初始化

物品重量价值初始化

编号重量价值
1w1=2v1=6
2w2=2v2=3
3w3=6v3=5
4w4=5v4=4
5w5=4v5=6
int[] w = {2,2,6,5,4};

int[] v = {6,3,5,4,6};

物品数量:n=5

背包容量:sw=10

确定状态转移方程

我们在面对一个物品要不要装进去的时刻,要面对两个问题

  1. 能不能装进去
  2. 装进去之后是不是价值最高的
if(j<w[i]) 
        dp[i][j]=dp[i-1][j];
else
        dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);

我们看这个代码。

j:背包容量

w[i]:某一个物品的重量

dp:备忘录

首先判断能不能装,不能装那么我们这时的背包中的物品价值和保持不变,即dp[i][j]=dp[i-1][j];

能装的话,我们取装与不装之间的最大值

dp[i][j]=Math.max(dp[i-1][j],dp[i-1][j-w[i]]+v[i]);

完整代码

  public static void main(String[] args) {
        int n = 5;
        int[] w = {2, 2, 6, 5, 4};
        int[] v = {6, 3, 5, 4, 6};
        int sw = 10;
        int[][] dp = new int[n][sw + 1];
        for (int i = 0; i <= sw; i++) {
            if (i < w[0]) {
                dp[0][i] = 0;
            } else {
                dp[0][i] = v[0];
            }
        }
        for (int i = 1; i < n; i++) {
            for (int j = 0; j <= sw; j++) {
                if (j < w[i]) {
                    dp[i][j] = dp[i - 1][j];
                } else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
                }
            }
        }
        System.out.println(dp[n - 1][sw]);

    }

备忘录与流程图

请添加图片描述

请添加图片描述

时间复杂度

在这串代码中,决定时间复杂度的是

	for (int i = 1; i < n; i++) {
            for (int j = 0; j <= sw; j++) {
                if (j < w[i]) {
                    dp[i][j] = dp[i - 1][j];
                } else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w[i]] + v[i]);
                }
            }
        }

很明显复杂度只体现在自己所做的备忘录中,在备忘录中进行遍历的时间复杂度:O(n*sw)

测试与答案

15

最长公共子序列问题

给定两个字符串,计算两个字符串的最大公共子串的长度。

子问题

每一个字母相同

确定状态

相同或者不相同

初始化

确定两个字符串text1text2及其长度。

确定状态转移方程

首先判断两个字母是否一样,一样的话就计数长度加一,不一样的话长度不变。

            for (int j = 1; j <= n; j++) {
                char c2 = text2.charAt(j - 1);
                if (c1 == c2) {
                    dp[i][j] = dp[i - 1][j - 1] + 1;
                } else {
                    dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]);
                }
            }

完整代码

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);
        String text1 = sc.next();
        String text2 = sc.next();
        int ans = longestCommonSubsequence(text1, text2);
        System.out.print(ans);
    }

    public static int longestCommonSubsequence(String text1, String text2) {

        int m = text1.length(), n = text2.length();
        int[][] dp = new int[m + 1][n + 1];
        for (int i = 1; i <= m; i++) {
            char c1 = text1.charAt(i - 1);
            for (int j = 1; j <= n; j++) {
                char c2 = text2.charAt(j - 1);
                if (c1 == c2) {
                    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[m][n];
    }

备忘录与流程图

请添加图片描述
请添加图片描述

时间复杂度

同上,此时为O(n*m)

测试与答案

请添加图片描述

石子合并问题

有N堆石子排成一排(n<=100),现要将石子有次序地合并成一堆,规定每次只能选相邻的两堆合并成一堆,并将新的一堆的石子数,记为该次合并的得分,编一程序,给出堆数n及每堆石子数(<=200);

(1)选择一种合并石子的方案,使得做n-1次合并,得分的总和最少

(2)选择一种合并石子的方案,使得做n-1次合并,得分的总和最多

子问题

每次合并即为一个子问题

确定状态

每次合并所得出来的值,在这题中有两个状态,最大值与最小值。

状态转移方程

我们先找到最大值与最小值,并进行比较,比较之后进行覆盖。

                for (int k = i + 1; k < j; k++) {
                    int t = mm[i][k] + mm[k + 1][j] + sum(i, j);
                    if (t < mm[i][j]) {
                        mm[i][j] = t;
                    }
                    if (t > MM[i][j]) {
                        MM[i][j] = t;
                    }
                }

完整代码

public class Stone_merge {
    static int[] aa;
    static int[][] mm;
    static int[][] MM;

    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        int n = sc.nextInt();
        aa = new int[n];

        for (int i = 0; i < n; i++) {
            aa[i] = sc.nextInt();

        }
        mm = new int[n + 1][n + 1];
        MM = new int[n + 1][n + 1];
        solve(n);
        System.out.println(mm[1][n]);
        System.out.println(MM[1][n]);

    }

    private static void solve(int n) {
        for (int i = 1; i <= n; i++) {
            mm[i][i] = 0;
            MM[i][i] = 0;
        }

        for (int r = 2; r <= n; r++) {
            for (int i = 1; i <= n - r + 1; i++) {
                int j = i + r - 1;
                mm[i][j] = mm[i][i] + mm[i + 1][j] + sum(i, j);
                MM[i][j] = MM[i][i] + MM[i + 1][j] + sum(i, j);
                for (int k = i + 1; k < j; k++) {
                    int t = mm[i][k] + mm[k + 1][j] + sum(i, j);
                    if (t < mm[i][j]) {
                        mm[i][j] = t;
                    }
                    if (t > MM[i][j]) {
                        MM[i][j] = t;
                    }
                }

            }

        }
    }

    private static int sum(int i, int j) {
        int sum = 0;
        for (int z = i - 1; z <= j - 1; z++) {
            sum += aa[z];
        }
        return sum;
    }
}

备忘录与流程图

请添加图片描述

请添加图片描述
请添加图片描述

时间复杂度

O(n^2)

测试与答案

请添加图片描述

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

申也.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值