动态规划总结

动态规划的的四个解题步骤是:

  • 定义子问题
  • 写出子问题的递推关系
  • 确定 DP 数组的计算顺序
  • 空间优化(可选)

1、重点还是状态转移矩阵的问题。找出转移矩阵,矩阵的含义就是题目要求的含义。找出dp[i]与dp[i-1].dp[i-2]等前态的关系。或者二维数组

稍微接触过一点动态规划的朋友都知道动态规划有一个“子问题”的定义。什么是子问题?子问题是和原问题相似,但规模较小的问题。例如这道小偷问题,原问题是“从全部房子中能偷到的最大金额”,将问题的规模缩小,子问题就是“从 kk 个房子中能偷到的最大金额”,用 f(k)f(k) 表示。

这要求子问题需要具备两个性质:

原问题要能由子问题表示。例如这道小偷问题中,k=nk=n 时实际上就是原问题。否则,解了半天子问题还是解不出原问题,那子问题岂不是白解了。
一个子问题的解要能通过其他子问题的解求出(是否包括求最值Math.max/min,加入本值nums[i])例如这道小偷问题中,f(k)f(k) 可以由 f(k-1)f(k−1) 和 f(k-2)f(k−2) 求出,具体原理后面会解释。这个性质就是教科书中所说的“最优子结构”。如果定义不出这样的子问题,那么这道题实际上没法用动态规划解。
小偷问题由于比较简单,定义子问题实际上是很直观的。一些比较难的动态规划题目可能需要一些定义子问题的技巧。

空间优化的基本原理是,很多时候我们并不需要始终持有全部的 DP 数组。对于小偷问题,我们发现,最后一步计算 f(n)f(n) 的时候,实际上只用到了 f(n-1)f(n−1) 和 f(n-2)f(n−2) 的结果。n-3n−3 之前的子问题,实际上早就已经用不到了。二维的矩阵,看是否可以直接将原矩阵当为dp;对于这种二维dp,且状态转移是关于dp[i][j-1],dp[i-1][j],可以直接将dp设为一维的,就是将原本的dp所有的横坐标去除即可。

明确是求和(每个dp[i]都算还是最后一个才算)还是求最值

数组分割:连续的,以某个元素为尾的最大、最小值。以最后一个为输出

数分割:不连续(不能有i-1,i-2推出),以某个元素为尾的最大、最小值(dp[n] 表示以 Sn 结尾)。且不一定是哪个前序对于每个dp[i],for(int j=0;j<i;j++)(j为分割成的某个数)然后在for循环中求最值(在第一层for循环里初始化),若是递归关系含有dp[i-X],dp[j-X],可以设置dp[n+1]不一定以最后一个输出(判断结果是否包括最后一个元素)

最长递增子序列;属于数分割不连续,但是这个有个数组,dp[n] 表示以 Sn 结尾的序列的最长递增子序列长度。不一定是哪个前序对于每个dp[i],for(int j=0;j<i;j++)(j为分割成的某个数)然后在for循环中求最值(在第一层for循环里初始化),若是递归关系含有dp[i-X],dp[j-X],可以设置dp[n+1],不一定以最后一个输出(判断结果是否包括最后一个元素)

最长公共子序列:

  • 在最长公共子序列中,dp[i][j] 表示 S1 中前 i 个字符与 S2 中前 j 个字符的最长公共子序列长度,不一定包含 S1i 和 S2j。
  • 在求最终解时,最长公共子序列中 dp[N][M] 就是最终解
  • 设计两个数组的用二维dp
  • 其递归函数是与dp[i-1]状态有关的固定的,因此是和一般动态规划一样,初始化,最后即为所求,两层递归函数(二维)

尝试修改初始值

求最值:Math.max/min(dp[j],dp[j-1])+num[i][j];

求次数:dp[j]+dp[j-1]

0-1背包

背包定义:
那么什么样的问题可以被称作为背包问题?换言之,我们拿到题目如何透过题目的不同包装形式看到里面背包问题的不变内核呢?
我对背包问题定义的理解:
给定一个背包容量target,再给定一个数组nums(物品),能否按一定方式选取nums中的元素得到target
注意:
1、背包容量target和物品nums的类型可能是数,也可能是字符串
2、target可能题目已经给出(显式),也可能是需要我们从题目的信息中挖掘出来(非显式)(常见的非显式target比如sum/2等)
3、选取方式有常见的一下几种:每个元素选一次/每个元素选多次/选元素进行排列组合
那么对应的背包问题就是下面我们要讲的背包分类

常见的背包类型主要有以下几种:
1、0/1背包问题:每个元素最多选取一次
2、完全背包问题:每个元素可以重复选择
3、组合背包问题:背包中的物品要考虑顺序
4、分组背包问题:不止一个背包,需要遍历每个背包

而每个背包问题要求的也是不同的,按照所求问题分类,又可以分为以下几种:
1、最值问题:要求最大值/最小值
2、存在问题:是否存在…………,满足…………
3、组合问题:求所有满足……的排列组合数

因此把背包类型和问题类型结合起来就会出现以下细分的题目类型:
1、0/1背包最值问题
2、0/1背包存在问题
3、0/1背包组合问题
4、完全背包最值问题
5、完全背包存在问题
6、完全背包组合问题
7、分组背包最值问题
8、分组背包存在问题
9、分组背包组合问题
这九类问题我认为几乎可以涵盖力扣上所有的背包问题

 在指定区间内寻找某些元素满足定值

(1)定义dp数组(m+1,n+1) 

(2)初始化第一行,因为状态转移函数是关于dp[i-1][j]的。dp[0][0] 求最值的话,初始化为最差情况,别的存在、组合数按定义填写.

例如:
最小:

Arrays.fill(dp,n+1);

        dp[0]=0;

最大:

int[] dp = new int[W + 1];

组合数:

dp[0]=1;

存在

 boolean[]dp=new boolean[sum/2+1];

        dp[0]=true;

(3)两个for循环,基础的是dp[i][j] = dp[i - 1][j];,当满足j >= w)时是dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w] + v);

首先是背包分类的模板:
1、0/1背包:外循环nums,内循环target,target倒序且target>=nums[i-1];
2、完全背包:外循环nums,内循环target,target正序且target>=nums[i];
3、组合背包(考虑顺序):外循环target,内循环nums,target正序且target>=nums[i];(不考虑顺序)外循环nums,(0-1背包)内循环target倒序且target>=nums[i-1](完全背包)内循环target正序且target>=nums[i]
4、分组背包:这个比较特殊,需要三重循环:外循环背包bags,内部两层循环根据题目的要求转化为1,2,3三种背包类型的模板

即根据求和、最值、存在确定初始化——根据是否是0-1背包和完全背包确定target的正反序——在根据是否按顺序安排num和target循环的前后顺序——再根据求和、最值、存在确定计算公式。

然后是问题分类的模板:
1、最值问题: dp[i] = max/min(dp[i], dp[i-nums]+1)或dp[i] = max/min(dp[i], dp[i-num]+nums);
2、存在问题(bool):dp[i]=dp[i]||dp[i-num];
3、组合问题:dp[i]+=dp[i-num];

不同的问题对于dp[i - 1][j], dp[i - 1][j - w] + v);的操作也不一样,如果是求总数,就是求和,如果是求是否就是或运算,求最值就是Math.max/min

// W 为背包总体积
// N 为物品数量
// weights 数组存储 N 个物品的重量
// values 数组存储 N 个物品的价值
public int knapsack(int W, int N, int[] weights, int[] values) {
    int[][] dp = new int[N + 1][W + 1];
    for (int i = 1; i <= N; i++) {
        int w = weights[i - 1], v = values[i - 1];
        for (int j = 1; j <= W; j++) {
            if (j >= w) {
                dp[i][j] = Math.max(dp[i - 1][j], dp[i - 1][j - w] + v);
            } else {
                dp[i][j] = dp[i - 1][j];
            }
        }
    }
    return dp[N][W];
}

public int knapsack(int W, int N, int[] weights, int[] values) {
    int[] dp = new int[W + 1];
    for (int i = 1; i <= N; i++) {
        int w = weights[i - 1], v = values[i - 1];
        for (int j = W; j >= 1; j--) {
            if (j >= w) {
                dp[j] = Math.max(dp[j], dp[j - w] + v);
            }
        }
    }
    return dp[W];
}

一篇文章吃透背包问题!(细致引入+解题模板+例题分析+代码呈现) - 零钱兑换 - 力扣(LeetCode) (leetcode-cn.com)https://leetcode-cn.com/problems/coin-change/solution/yi-pian-wen-zhang-chi-tou-bei-bao-wen-ti-sq9n/

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值