美文网首页
DP: Dynamic Programming

DP: Dynamic Programming

作者: Jimmy木 | 来源:发表于2019-10-15 16:09 被阅读0次

    概述

    动态规划是为了避免递归中出现重复计算的一种策略.

    对于子问题重叠的情况特别有效, 因为他将子问题解保存在表格中, 当需要某个子问题的解时,可以直接取值.

    核心思想

    将待求解问题分解为若干子问题, 子问题与子问题有重叠部分.

    按顺序求解子问题, 前一子问题为后一子问题的求解提供了有效信息.

    例如从n=1开始解决, 递推到n=N, 求出最终值.

    1. 寻找最优子结构;
    2. 列出递归方程, 自底向上对每个子结构解一次并保留到表中, 需要时在表中查找;
    3. 找到最优解;

    应用场景

    问题可以分解为若干子问题并可以独立求解子问题.
    子问题有重叠.

    求解步骤

    1. 分析最优解的性质, 并刻画其结构特征;
    2. 定义最优解变量, 定义最优解公式;
    3. 自低向上计算最优解;
    4. 构造问题最优解;

    示例1: 走楼梯

    走楼梯: 每次走楼梯可以走1级或2级, 一共有多少种走法

    解析: f(n) = f(n-1)+f(n-2)

    int countStep(int n) {
        if (n < 1) return 0;
        if (n == 1) return 1;
        if (n == 2) return 2;
    
        int res = 0;
        int a = 1;
        int b = 2;
        for (int i = 3; i <= n; i++) {
            res = a + b;
            a = b;
            b = res;
        }
        return res;
    }
    

    示例2: 机床切割

    最优解: 假设5台机床, 每台由不同人来操作, 每台机器能操作的零件数量不一样. 工人总数为10. 每台机床的零件数和工人数:50(3),100(4),250(5),300(5),190(4). 求最大零件数.

    解析: 当只考虑1台机床时, 找到最大零件数. 当2台机床的时候, 可以从机床1抽取人去第二台机床, 也可以不开第二台机床, 求出最大值.

    P[i]表示第i台机床需要的工人数量, G[i]表示第i台机床零件数
    F(i,j)表示第i台机床j个操作工人的最大零件数.
    F(i,j)=max(F(i-1,j),F(i-1,j-P[i-1]+G[n-1]))

    示例3: 男女排座位

    座位问题: n个人坐成一排, 有男生和女生, 女生边上至少要有一个女生.

    分析: dp[i][0]表示i个人的排座方式且最后一个为男生, dp[i][1]表示i个人的排座方式且最后一个为女生.

    如果最后一个为男生: dp[i][0] = dp[i-1][0] + dp[i-1][1], 最后一个是男生,就无所谓前面是男生还是女生.
    如果最后一个为女生: dp[i][1] = dp[i-2][0] + dp[i-1][1], 最后一个是女生倒数第二个就不能是男生, 但是dp[i-1][i]未包含倒数第2个前面是男生的情况.

    int seats(int n) {
        vector<vector<int>> dp(n+1, vector<int>(2 ,0));
        dp[1][0] = 1;
        dp[2][0] = 1;
        dp[2][1] = 1;
    
        for (int i = 3;i <= n; i++) {
            dp[i][0] = dp[i-1][0] + dp[i-1][1];
            dp[i][1] = dp[i-2][0] + dp[i-1][1];
        }
    
        return dp[n][0] + dp[n][1];
    }
    

    示例4: 邮票求和

    邮票问题: 判断若干邮票组合成特定值最少需要多少张, 邮票面值按升序排列. 如果5票邮票1,3,3,3,4组合7最少需要2张, 无解输出0.

    解析: 先排第i张邮票, 假如不选中就是dp[num], 假如选中就是dp[num-vec[i]]+1.

    int stamps(int num, vector<int> &vec) {
        int count = (int)vec.size();
        // 对应i值需要多少张邮票
        vector<int> dp(num+1, 1000);
        dp[0] = 0;
        for (int i = 0; i < count; i++) {
            for (int j = num; j >= vec[i] ; j--) {
                dp[j] = min(dp[j], dp[j-vec[i]] + 1);
            }
    
        }
    
        return dp[num];
    }
    

    示例5: 分水果

    有若干不同重量水果, 将水果分成两份, 求分成两份后最小重量差值.

    分析: 求出最接近总重量中间值的水果重量, 即可求出小份水果重量.

    // 分水果问题
    // 有若干重量不等水果, 划分为2份水果, 求两份水果最小差值
    int fruit(vector<int> vec) {
        int num = 0;
        for(int i = 0;i < vec.size();i++) {
            num += vec[i];
        }
        // 计算出最接近总量中间值的重量即可求出差值
        int half = num / 2;
        // num / 2舍去部分小数, 所以num / 2 + 1
        vector<int> dp(num / 2 + 1, 0);
    
        for (int i = 0;i < vec.size(); i++) {
            for (int j = half; j >= vec[i]; j--) {
                // 不选取该水果为dp[j], 选取该水果为dp[j-vec[i]]+vec[i]
                dp[j] = max(dp[j], dp[j-vec[i]] + vec[i]);
            }
        }
        // 因为除以2舍去部分小数, dp[half]一定小于num - dp[half]
        return num - 2 * dp[half];
    }
    

    示例6: 两船装货

    有两艘船和若干货物, 判断两船是否可以装完这些货物.

    分析: 先尽量装满小船, 然后判断剩下货物是否可以装上大船.

    bool boat(int a,int b, vector<int> vec) {
        int num = 0;
        for(int i = 0;i < vec.size();i++) {
               num += vec[i];
        }
        int c = a+ b;
        a = min(a, b);
        b = c - a;
        vector<int> dp(a+1, 0);
    
        for (int i = 0;i < vec.size(); i++) {
            for (int j = a; j >= vec[i]; j--) {
                dp[j] = max(dp[j], dp[j-vec[i]]+vec[i]);
            }
        }
    
        return num - dp[a] < b;
    }
    

    示例7: 安排工作

    安排工作: 有一些工作, 每个工作有开始时间/结束时间/报酬, 一次只能完成一份工作, 如何安排工作可以获得最大报酬.

    解析: 设置dp[i]为前i个工作能获得的最大报酬.
    对于第i个项目, 前面第j个项目已满足条件.
    如果做第i个项目: dp[i] = dp[j]+ jobs[i].value
    如果不做第i个项目: dp[i] = dp[i-1]

    int jobPlanCmp(vector<int> a, vector<int> b) {
        return a[0] < b[0];
    }
    // 项目安排
    // 有多个项目可以做, 每个项目有开始时间/结束时间/报酬, 一次只能做一个项目, 如何获取最大报酬
    // 解析: 根据时间规划最大报酬
    int jobPlan(vector<vector<int>> &jobs) {
        int n = (int)jobs.size();
        // 按项目开始时间排序
        sort(jobs.begin(), jobs.end(), jobPlanCmp);
    
        vector<int> dp(n + 1, 0);
        // dp[i]表示前i个项目的最大报酬
        dp[1] = jobs[1][2];
        int j = 0;
        for(int i = 1;i <= n; i++) {
            for (j = i - 1; j >= 1; j--) {
                if (jobs[j-1][1] <= jobs[i-1][0]) {
                    break;
                }
            }
            dp[i] = max(dp[j] + jobs[i-1][2], dp[i-1]);
        }
    
        return dp[n];
    }
    

    示例8: 导弹拦截

    拦截导弹: 有一批高度不一样的导弹, 一次可以拦截多个导弹,拦截导弹的高度只能递减, 一次最多拦截多少导弹.

    解析: dp[i]表示拦截第i颗导弹时, 最大可以拦截的导弹数.
    如果拦截前i颗的导弹数就不知道前i颗导弹的具体高度是多少.

    int missile(vector<int> vec) {
        int n = (int)vec.size();
        // 拦截第i个导弹最多拦截数
        vector<int> dp(n+1, 0);
        dp[1] = 1;
        int res = 1;
        for (int i = 2; i <= n; i++) {
            int ii = i - 1; // 当前导弹序号
            for (int jj = 0; jj < ii; jj++) {
                if (vec[jj] >= vec[ii] && dp[jj+1] >= dp[i]) {
                    dp[i] = dp[jj+1] + 1;
                    res = max(res, dp[i]);
                }
            }
        }
        return res;
    }
    

    示例9: 最大递增子序列

    最大递增子序列, 给定一个数组, 输出最大递增子序列个数

    解析: dp[i]表示包含第i个元素的子序列个数. 当前面第j个元素比当前元素小, 子序列个数加1.

    int subSeriel(vector<int> vec) {
        int n = (int)vec.size();
        vector<int> dp(n+1, 1);
        
        dp[0] = 0;
        for (int i = 2; i <= n; i++) {
            int ii = i-1;
            for (int jj = 0;jj < ii;jj++) {
                int j = jj + 1;
                if (vec[ii] > vec[jj] && dp[j] >= dp[i]) {
                    dp[i] = dp[j] + 1;
                }
            }
        }
    
        return dp[n];
    }
    

    示例10: 出入栈问题

    给定一个数字, 表示操作次数, 最初和最后栈都为空. 输出一共有多少种操作序列.

    解析: dp[i][j]表示进行i次入栈和j次出栈的操作总数.
    如果i和j相等时, dp[i][j]=dp[i][j-1], 因为出栈数和入栈数总是相等, 最后一次必为入栈;
    如果i和j不相等时, dp[i][j] = dp[i-1][j] + dp[i][j-1];
    只入不出只有一种情况, dp[i][0] = 1;

    int stack(int n) {
        if(n % 2 == 1) return 0;
        // i个操作的序列数
        vector<vector<int>> dp(n/2 + 1, vector<int>(n/2+1,0));
        for (int i = 0; i < n/2 + 1; i++) {
            dp[i][0] = 1;
        }
    
        for (int i = 1; i <= n / 2; i++) {
            for (int j = 1; j <= i; j++) {
                if (i == j) {
                    dp[i][j] = dp[i][j-1];
                } else {
                    dp[i][j] = dp[i-1][j] + dp[i][j-1];
                }
            }
        }
    
        return dp[n/2][n/2];
    }

    相关文章

      网友评论

          本文标题:DP: Dynamic Programming

          本文链接:https://www.haomeiwen.com/subject/lzstmctx.html