赞
踩
终于到这个专题啦 ~~~ 激动的搓手手!
动态规划:Dynamic Programming (DP),如果某一问题有很多重叠子问题,实验DP是最有效的
因此只要是 当前状态可以根据前面的状态推出来 的题型,就能用动归。
动态规划中每一个状态一定是由上一个状态推导出来的,这一点区分与贪心,贪心没有状态推导,而是从局部直接选最优、
贪心解决不了动态规划的问题。
要求:最优子结构可以通过把它分解成子问题,然后递归地找到子问题的最优解来得到最优解。重叠子问题子问题是重叠的,这样我们只能计算一次,并存储解决方案以备将来使用降低时间复杂度(指数到多项式)如果子问题不重叠->分治无后效应当一个子问题被用于求解一个更大的问题时,它的最优解不会改变
自上而下的:记忆化递归(从大到小)
自底向上:DP (从小到大)
使用DP的算法:
斐波那契序列
LCS
背包
弗洛伊德
沃沙尔bellman
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-n2gH09KX-1624203830729)(C:\Users\lh\AppData\Roaming\Typora\typora-user-images\image-20210620125840171.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UvlOTyaj-1624203830735)(C:\Users\lh\AppData\Roaming\Typora\typora-user-images\image-20210620132135592.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HuhNBM4v-1624203830741)(C:\Users\lh\AppData\Roaming\Typora\typora-user-images\image-20210620132255046.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eUURX1j2-1624203830749)(C:\Users\lh\AppData\Roaming\Typora\typora-user-images\image-20210620132304314.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-39bohMae-1624203830751)(C:\Users\lh\AppData\Roaming\Typora\typora-user-images\image-20210620132542996.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-39lBWtQh-1624203830752)(C:\Users\lh\AppData\Roaming\Typora\typora-user-images\image-20210620132324780.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-24yGeDIr-1624203830754)(C:\Users\lh\AppData\Roaming\Typora\typora-user-images\image-20210620132350512.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RyI6Dj7s-1624203830755)(C:\Users\lh\AppData\Roaming\Typora\typora-user-images\image-20210620132513639.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aRLCpSpE-1624203830758)(C:\Users\lh\AppData\Roaming\Typora\typora-user-images\image-20210620132528137.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XCySdUVj-1624203830759)(C:\Users\lh\AppData\Roaming\Typora\typora-user-images\image-20210620133111881.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HhqyjJJi-1624203830760)(C:\Users\lh\AppData\Roaming\Typora\typora-user-images\image-20210620133121348.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dzmWUvfE-1624203830769)(C:\Users\lh\AppData\Roaming\Typora\typora-user-images\image-20210620133134412.png)]
状态转移公式是很重要的,但是DP不仅仅只有递推公式。
动态规划五部曲:
注意:是先确定地推公式,在考虑初始化。因为某些情况下,是递推公式决定了要如何初始化。
这里的5个步骤,都必须清楚明了才能真正理解DP的整个过程哦~切忌仅把目光放在递推公式上。
找问题的最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的!所以要关注 dp 数组!!!
遇到问题时,其实可以自己先思考这三个问题:
要清楚自己是哪里不明白:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-j3QGEfh6-1624203830770)(C:\Users\lh\AppData\Roaming\Typora\typora-user-images\image-20210610184012519.png)]
主要掌握 01 背包和完全背包,最多再来一个 多重背包 就够用了,力扣上连多重背包的问题都没有~
有N件物品,和一个能背重量为W的背包,第 i 件物品的重量是 weight[i],得到的价值为 value[i],每件物品只能用一次,求解将哪些物品装入背包里的物品价值总和最大。
例题:假设 背包的最大重量为 4,物品如表中所示:
解法一:暴力搜素,回溯法
每件物品的状态只有两个,取 或者不取,使用回溯搜索出所有的情况,时间复杂度为 O(2^n),n 代表物品数量
解法二:动态规划
对回溯法的优化。
动态规划五部曲:
1、确定dp数组以及下标的含义
2、确定递推公式,即状态转移方程
有两个方向可以推出来 dp[i] [j],也就是对于第 i 个物品,有两种状态,取 或者是 不取,所以需要有判断条件
如果不取:由 dp[i - 1] [j] 推出,即背包容量为 j,里面不放物品 i 的最大价值,也就是当 背包的 容量 j 小于 物品 i 的重量时 ,此时dp[i] [j]就是dp[i - 1] [j]
如果取: 由 dp[i - 1] [j - weight[i]] 推出,也就是当 背包的 容量 j 大于或者等于 物品 i 的重量时 ,dp[i - 1] [j - weight[i]] 为背包容量为j - weight[i]的时候不放物品i的最大价值,
那么 dp[i - 1] [j - weight[i]] + value[i] (物品i的价值) ,就是背包放物品i得到的最大价值。
所以递推公式:dp[i] [j] = max(dp[i - 1] [j], dp[i - 1] [j - weight[i]] + value[i]);
3、dp 数组的初始化
两个方向,一个是 i = 0 时,一个是 j = 0 时
对于 j = 0,在初始化 dp 数组的时候直接初始化,因为当背包所能承受最大重量为0时,背包价值只能是 0。
对于 i = 0,要使用倒序遍历,用 for 循环,j 从后往前遍历;因为如果使用 正序遍历,物品 0 就会被重复加入多次。
注意这里容易出错,一定要是倒序遍历,保证物品0只被放入一次,这一点对 01背包 很重要。经过初始化之后, dp 数组的第一行和第一列就完成了初始化。
对于 dp 数组中的其他数组,如果题目中没有负数的价值,可以直接初始化为 0,
如果出现了负数的价值,则需要将其初始化为 负无穷 INT_MIN
Attention:初始化的时候可以不加dp[0] [j-weight[0]],就算只有value[0]也可以,但是加上是为了 与 滚动数组的写法相呼应。
// 第一种 先遍历物品,然后遍历背包重量 // weight数组的大小 就是物品个数 for(int i = 1; i < weight.size(); i++) { // 遍历物品 for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 if (j < weight[i]) dp[i][j] = dp[i - 1][j]; // 这个是为了展现dp数组里元素的变化 else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); } } // 先遍历物品,然后遍历背包重量 也可以写成如下的方式: // 这两种方式打印出的 dp 数组是不同的,第二种遍历方式得到的左下角元素为 0 其实空出来的 0 ,是用不上的。 // 第二种 遍历过程 for(int i = 1; i < weight.size(); i++) { // 遍历物品 for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量 if (j - weight[i] >= 0) { dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); } } }
// 第三种 先遍历背包重量,然后遍历物品
// weight数组的大小 就是物品个数
for(int j = 0; j <= bagWeight; j++) { // 遍历背包容量
for(int i = 1; i < weight.size(); i++) { // 遍历物品
if (j < weight[i]) dp[i][j] = dp[i - 1][j];
else dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]);
}
}
// 完整代码 void test_2_wei_bag_problem1() { vector<int> weight = { 1, 3, 4 }; // 物品重量 vector<int> value = { 15, 20, 30 }; // 物品价值 int object = 3; // 物品数量 int bagWeight = 4; // 背包容量 // 定义 dp 数组 vector<vector<int>> dp(object, vector<int>(bagWeight + 1, 0)); // 初始化 // j = 0 的情况就是 0,相当于在定义时就已经初始化了 // 易出错点一::终止条件处 for (int j = bagWeight; j >= weight[0]; j--) { // 这里初始化有两种写法,都可以的,第二种是为了呼应接下来学习的滚动数组 //dp[0][j] = value[0]; dp[0][j] = value[0] + dp[0][j - weight[0]]; } // 遍历 dp 数组 for (int i = 1; i < object; i++) // 遍历物品 { for (int j = 1; j <= bagWeight; j++) // 遍历背包容量 { // 易出错点二::终止条件处 // // 判断的方法一:这里的判断条件忘记了!!!!!!!! // 常错项 max 的第二个元素,value[i] + dp[j - weight[i]],这个 dp 老是写错。 if (j < weight[i]) dp[i][j] = dp[i - 1][j]; else dp[i][j] = max(dp[i - 1][j], value[i] + dp[i - 1][j - weight[i]]); // // 判断的方法二: //if (j - weight[i] >= 0) // dp[i][j] = max(dp[i - 1][j], dp[i - 1][j - weight[i]] + value[i]); cout << dp[i][j] << " "; } cout << endl; } } int main() { test_2_wei_bag_problem1(); return 0; }
对于背包问题,其状态都是可以压缩的,也就是把二维变成一维,
也就是上一层可以重复利用,将上一层直接拷贝到当前层,这就是滚动数组的由来。
将 dp[i] [j] 压缩为 dp[j]。dp[i] [j] 的含义是 从 0 到 i 个物品中任意抽取,并放进容量为 j 的背包中,其价值总和最大为 dp[j] 。
动态规划五部曲:
// 完整代码 void test_1_wei_bag_problem() { vector<int> weight = { 1, 3, 4 }; vector<int> value = { 15, 20, 30 }; int object = weight.size(); int bagWeight = 4; // 初始化 vector<int> dp(bagWeight + 1, 0); for (int i = 0; i < object; i++) // 遍历物品 { for (int j = bagWeight; j >= weight[i]; j--) // 遍历背包容量 { // 常错项 max 的第二个元素,value[i] + dp[j - weight[i]],这个 dp 老是写错。 dp[j] = max(dp[j], value[i] + dp[j - weight[i]]); } // 打印日志 for (int k = 0; k < dp.size(); k++) { cout << dp[k] << " "; } cout << endl; } } int main() { test_1_wei_bag_problem(); }
有N件物品和一个最多能背重量为W的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品都有无限个(也就是可以放入背包多次),求解将哪些物品装入背包里物品价值总和最大。
完全背包和 01 背包唯一不同的就是,每种物品有无限件。
要点一:完全背包的一维 dp 数组内层循环遍历顺序是从小到大,
要点二:为什么物品遍历在外层,背包容量遍历在内层?
对于 纯完全背包 问题,不用区分是求组合数还是求排列数,
因为纯完全背包是 求解将哪些物品装入背包里物品价值总和最大,此时先遍历物品还是先遍历物品是一样的
但是利用完全背包求解有多少种方式的时候,就要区别是组合数还是排列数了
要分清楚是求组合数还是求排列数
求组合数:外层遍历物品,内层遍历背包,都是正序从小到大进行
求排列数:外层遍历背包,内层遍历物品,都是正序从小到大进行
// 完整代码 void test_CompletePack() { vector<int> weight{1, 3, 4}; vector<int> value{15, 20, 30}; int object = weight.size(); int bagWeight = 4; // 初始化 vector<int> dp(bagWeight + 1, 0); // 遍历方式一:外层遍历物品,内层遍历背包容量 for(int i = 0; i < object; i++) { // 注意这里终止条件是有个等号的 for(int j = weight[i]; j <= bagWeight; j++) { // 常错项 max 的第二个元素,value[i] + dp[j - weight[i]],这个 dp 老是写错。 dp[j] = max(dp[j], value[i] + dp[j - weight[i]]); } // 打印日志 for (int k = 0; k < dp.size(); k++) { cout << dp[k] << " "; } cout << endl; } 遍历方式二:外层遍历背包容量,内层遍历物品 //for (int j = 0; j <= bagWeight; j++) //{ // for (int i = 0; i < object; i++) // { // if (j - weight[i] >= 0) // { // // 常错项 max 的第二个元素,value[i] + dp[j - weight[i]],这个 dp 老是写错。 // dp[j] = max(dp[j], value[i] + dp[j - weight[i]]); // } // } // // 打印日志 // for (int k = 0; k < dp.size(); k++) // { // cout << dp[k] << " "; // } // cout << endl; //} } int main() { test_CompletePack(); }
有N种物品和一个容量为V 的背包。第i种物品最多有Mi件可用,每件耗费的空间是Ci ,价值是Wi 。求解将哪些物品装入背包可使这些物品的耗费的空间 总和不超过背包容量,且价值总和最大。
动态规划五部曲:
如果代码写出来不对,就把dp数组打印出来,看看和我们推导的数列是不是一致。
规律描述:
爬到第一层楼梯有一种方法,爬到第二层楼梯有两种方法
那么爬到第三层的方法可以由第一层和第二层的状态推导出来:
第一层楼梯再夸两步就到第三层
第二层楼梯再夸一步就到第三层
动态规划五部曲:
递推公式 dp[i] [j] = dp[i - 1] [j] + dp[i] [j - 1]: dp[i] [j]定义 :表示从(0 ,0)出发,到(i, j) 有dp[i] [j]条不同的路径
递推公式:dp[i] = max(dp[i], max(i * (j - i), j * dp[i - j])) dp[i] 分拆数字 i,可以得到的最大乘积为 dp[i]
递推公式: dp[i] = dp[i] + dp[j - 1]*dp[i-j] dp[j-1]是以 j-1 为头结点左子树节点数量, dp[i-j]是以j为头结点右子树节点数量
转化为 01背包问题:
确定以下问题:
动态规划五部曲:
1、确定dp数组以及下标的含义
2、确定递归公式
3、dp数组初始化
4、确定遍历顺序
放在回溯的专题中了
转化为 01背包问题:
确定以下问题:
这道题与第416题的思路很相似,主要的不同分有以下几个:
动态规划五部曲:
1、确定dp数组以及下标的含义
2、确定递归公式
3、dp数组初始化
4、确定遍历顺序
分析:
式一:left - right = target; 式二: left + right = sum
式一 与 式二 相加可得 式三:2*left = target + sum
式三中 target 与 sum 都是已知且固定的,因此 left 可由此得出。
第一种解法:回溯法中的组合总和。
时间复杂度都是是O(2^n)级别,所以最后超时了。
为了加深自己的理解,自己写了一个版本,与 Carl的有差别,但是最后三个版本的都超时了
第二种解法:转换为 01背包问题
1、为什么是 01背包呢?
因为数组中的每个值只能用一次。
2、与之前的不同,之前都是求容量为 j 的背包可以装的最大价值,
而本题是 表示容量为 j 的背包装满有 dp[j] 种方法。
3、 复杂度分析
空间复杂度:O(m) m为背包容量
4、本题中是求装满有几种方法,这其实还是回归的组合
本题是等价为 01背包问题,只不过这个背包是有两个维度来决定的,一个是m, 一个是n
数组中不同长度的字符串就是不同大小的物品。
背包容量就是 m 和 n
自己写写 dp 数组有助于理解啊
动规五部曲:
1、确定 dp 数组以及下标的含义
2、确定递推公式
3、dp 数组如何初始化
4、确定遍历顺序
等价为 完全背包
本题等价为 完全背包问题
背包的最大容量为 n
物品 就是 完全平方数,第i个物品对应的重量也是 i,但是本题是要 ii,所以物品每次从 i 开始,到 n 为止,每次判断用 ii
问:装满背包所用物品的最小个数是多少个?
dp数组定义:dp[i] 表示背包容量为 i 时,装满背包所用物品的最小个数是 dp[i] 个
dp数组初始化:dp[0] = 1,其余的为 INT_MAX
递推公式 :dp[i] = min(dp[i], dp[i - coins[j]] + 1)
遍历顺序:因为是在装满背包的情况下所用物品的最小个数,因此遍历顺序是无所谓的
因为是完全背包,所以遍历时均为正序遍历
// // 错误1:dp[0] = 0 没有初始化
// // 错误2:外层遍历物品时的终止条件是 i*i<n,我刚刚忘记乘上i了
// // 错误3:内层的防止溢出有两个,一个是对于下标,要求 j-ii 大于或者等于 0,还有一个是 dp[j-ii] != INT_MAX
// // 注意 if (j - i * i >= 0 && dp[j - i * i ] != INT_MAX) 的等于号 ,刚刚我没有加等于号
在排列问题中,面试时可以考察:两个for循环的嵌套顺序,为什么target放外面,nums放里面。
因为这是一个组合问题,如果不这样的话,物品的顺序就是从 i 开始,这样就不会包括和相等但是顺序不同的了
对于 纯完全背包 问题,不用区分是求组合数还是求排列数,
因为纯完全背包是求解 将哪些物品装入背包里物品价值总和最大,此时先遍历物品还是先遍历物品是一样的
但是利用完全背包求解有多少种方式的时候,就要区别是组合数还是排列数了
要分清楚是求组合数还是求排列数
- 求组合数:外层遍历物品,内层遍历背包,都是正序从小到大进行
- 求排列数:外层遍历背包,内层遍历物品,都是正序从小到大进行
本题属于本题属于完全背包问题:
本题中的dp数组的下标和含义要依据题意来变一下
dp[j] 凑足总金额为 j 所需铅笔的最少个数为 dp[j]
求最小就需要将除了第一个元素之外的值 初始化为 INT_MAX,
同时,因为有了INT_MAX所以要多一句 if 判断来防止溢出。
动规五部曲:
1、确定dp数组及其下标含义
dp[j] 凑足总金额为 j 所需铅笔的最少个数为 dp[j]
2、确定递推公式
①:考虑coins[i]时: dp[j] = dp[j]
②:不考虑coins[i]时:dp[j] = 1 + dp[j - coins[i]]
取两者中的最小值:dp = min(dp[j], 1 + dp[j - coins[i]])
3、dp数组初始化
j = 0 时,对应的 dp[j] = 0
j 为其他下标时,dp[j] = INY_MAX,将其初始化为一个最大数,为了避免在 min() 求最小值比较的过程中被初始值覆盖
4、确定遍历顺序
本题不强调物品与背包的遍历顺序,两种都可以的
此处采用先遍历物品,再遍历背包容量,内层循环用正序问题:
本题中的 dp数组的下标 和含义要依据题意来变一下
dp[j] 凑足总金额为 j 所需铅笔的最少个数为 dp[j]
求最小就需要将除了第一个元素之外的值 初始化为 INT_MAX,
同时,因为有了INT_MAX所以要多一句 if 判断来防止溢出。
动规五部曲:
1、确定dp数组及其下标含义
2、确定递推公式
3、dp数组初始化
4、确定遍历顺序
本题等价为 完全背包 问题
组合数:先遍历物品,在遍历背包
第一:转换为 完全背包来理解 : 两个都是正序,从小到大进行遍历即可
第二:求组合数 : 先遍历物品,在遍历背包
第三:求装满背包有几种方法 : dp[i] = dp[i] + dp[j - nums[i]]
动规五部曲:
1、确定dp数组及其下标含义
2、确定递推公式
3、dp数组初始化
4、确定遍历顺序
打家劫舍是 dp 解决的经典问题。
1、确定 dp 数组及其下标的含义
2、确定递推公式
3、dp 数组初始化
4、确定遍历顺序
围城了环,那么就有三种情况
情况二和情况三已经包括了情况一,所以只需考虑后面两种情况即可,
很巧妙的解决了成环问题
树的遍历有四种,本题一定是要后序遍历的,因为需要通过递归函数返回值来考虑的返回值,来计算下一步的结果
无需重新定义 递归函数,本题中给的函数已经符合写递归的条件,直接写就好了嘛
(1)方法一:暴力递归
n 表示节点个数
时间复杂度:O(n^2) 这个时间复杂度不太标准,也不容易准确化,例如越往下的节点重复计算次数就越多
空间复杂度:O(n) 算上递推系统栈的空间
计算了root的四个孙子(左右孩子的孩子)为头结点的子树的情况,又计算了root的左右孩子为头结点的子树的情况,计算左右孩子的时候其实又把孙子计算了一遍。
(2)方法二:记忆化递推
时间复杂度:O(n)
空间复杂度:O(n) 算上递推系统栈的空间
要点就是使用一个 map 把计算过的结果保存一下,这样就可以对方法一进行优化,如果计算过孙子,那么计算孩子的时候可以重复用孙子的结果
(3)方法三:动态规划
上面两种方法对一个节点 偷与不偷得到的最大金钱都没有做记录,而是需要实时计算的,
而动态规划其实就是使用状态转移容器来记录状态的变化,这里可以使用一个长度为2的数组
记录当前节点偷与不偷所得到的最大金钱
本题可以称为 树形dp 的入门题目,也就是在树上进行状态转移,递归三部曲 + 动规五部曲
三种解题思路:
1、暴力解法
2、贪心解法
3、动规解法
动态规划五部曲:
1、确定 dp 数组以及下标的含义
注意::这里的持有或者不持有,并不代表是否买入,持有也可能是昨天就买入了,今天保持持有状态,但是今天并不买入
2、确定递推公式
3、dp 数组初始化
4、确定遍历顺序
将动规法用滚动数组做状态压缩,可以优化空间
动规五部曲:
1、确定 dp 数组以及下标的含义
2、确定递推公式
3、dp 数组初始化
4、确定遍历顺序
在上面分析的基础上进行空间的优化,因为 dp数组可以沿用前一个状态
动态规划五部曲:
1、确定 dp 数组以及下标的含义
2、确定递推公式
3、dp 数组如何初始化
4、确定遍历顺序
最长上升子序列是动规的经典题目之一,因为这里的 dp[i] 可以根据 dp[j] ( j < i ),推导出来。
动态规划五部曲
1、确定 dp 数组以及下标的含义
2、状态转移方程
3、dp[i] 的初始化
4、确定遍历顺序
动规五部曲分析如下:
1、确定dp数组(dp table)以及下标的含义
2、确定递推公式
3、dp数组初始化
4、确定遍历顺序
子序列 VS 子数组(连续子序列) 的处理不一样,好好细品上面几道题哦 ~
这道题目 其实是可以用双指针或者贪心的的
这道题目算是编辑距离的入门题目(毕竟这里只是涉及到减法),也是动态规划解决的经典题型。
这一类题都是题目读上去感觉很复杂,模拟一下也发现很复杂,用动规分析完了也感觉很复杂,但是最终代码却很简短。
编辑距离的题目最能体现出动规精髓和巧妙之处
动态规划五部曲
1、确定dp数组以及下标的含义
2、确定递推公式
3、dp 数组初始化
4、确定遍历顺序
这道题如果是求连续的子序列,那么就可以考虑 KMP 算法,这道题目双指针法可就做不了,然而本题是求 子序列。
动态规划五部曲:
1、确定 dp 数组以及下标的含义
2、确定递推公式
3、dp 数组初始化
4、确定遍历顺序
本题与 第115题 不同的就是,两个字符串都可以做删除操作,情况虽然复杂了,但是整体的思路是不变的。本题是两个字符串可以互相删。
动态规划五部曲:
1、确定 dp 数组以及下标含义
2、确定递推公式
3、dp 数组初始化
4、确定遍历顺序
编辑距离是用动规解决的经典题目,这题目看起来好像很复杂,但是实际上用动规可以很巧妙的算出最少的编辑距离。
动态规划五部曲
1、确定 dp 数组以及下标含义
2、确定递推公式
3、dp 数组初始化
4、确定遍历顺序
真的很哇塞!
question 1、回文串定义:
正着读和反着读都一样的字符串。
question 2、回文子串 VS 回文子序列
回文子串是要连续的,回文子序列可不是连续的
回文子串,回文子序列都是动态规划经典题目。
解题方法:
解法二:
动态规划五部曲:
1、确定 dp 数组以及下标的含义
2、确定递推公式
3、dp 数组初始化
4、确定遍历顺序
解法三:双指针法
先找中心,以中心向两边扩散,看看是不是对称,由此来判断是否是回文串。
在遍历中心点的时候,要注意中心点有两种情况。
本题是求回文子序列,也就是不要求连续咯~
本题可以用马拉车算法
动态规划五部曲:
3、dp 数组初始化
4、确定遍历顺序
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。