24.买卖股票的最佳时机系列
24.1买卖股票的最佳时机
你只能选择 某一天 买入这只股票,并选择在 未来的某一个不同的日子 卖出该股票。设计一个算法来计算你所能获取的最大利润。
只能一笔交易
class Solution {
public int maxProfit(int prices[]) {
if(prices.length < 2)return 0;
int[][] dp = new int[prices.length][2];
dp[0][0] = -prices[0];
dp[0][1] = 0;
for (int i = 1; i < prices.length; i++) {
dp[i][0] = Math.max(dp[i - 1][0] , -prices[i]);
dp[i][1] = Math.max(dp[i - 1][0] + prices[i], dp[i - 1][1]);
}
return dp[prices.length - 1][1];
}
}
思路:
- 确定dp数组中的含义:定义一个长度为2的dp数组,dp数组的两个位置分别表示两种状态,
dp[i][0]
表示第i天持有股票所得最多现金 ,一开始现金是0,那么加入第i天买入股票现金就是-prices[i]
;dp[i][1]
表示第 i 天不持有股票所得最多现金 - 确定递推公式:
dp[i][0]
(持有股票):可以由两个状态推出来:①第i-1
天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
②第i天买入股票,所得现金就是买入今天的股票后所得现金即:-prices[i]
。dp[i][0]
应该选所得现金最大的
dp[i][1]
(不持有股票):可以由两个状态推出来:①第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
②第i天卖出股票,所得现金就是按照今天股票佳价格卖出后所得现金即:prices[i] + dp[i - 1][0]
。同样dp[i][1]
取最大的 - dp数组初始化:由递推公式可以看出,都是要从
dp[0][0]
和dp[0][1]
推导出来。
dp[0][0]
表示第0天持有股票:此时的持有股票就一定是买入股票了,因为不可能有前一天推出来,所以dp[0][0] -= prices[0];
dp[0][1]
表示第0天不持有股票,不持有股票那么现金就是0,所以dp[0][1] = 0;
- 确定遍历顺序:从递推公式可以看出dp[i]都是有dp[i - 1]推导出来的,那么一定是从前向后遍历。
24.2买卖股票的最佳时机Ⅱ
任意多少笔交易都行
class Solution {
public int maxProfit(int[] prices) {
if(prices.length < 2)return 0;
int[][] dp = new int[prices.length][2];
dp[0][0] = -prices[0];
dp[0][1] = 0;
for (int i = 1; i < prices.length; i++) {
dp[i][0] = Math.max(dp[i - 1][0] , dp[i - 1][1] - prices[i]);
dp[i][1] = Math.max(dp[i - 1][1] , dp[i - 1][0] + prices[i]);
}
return dp[prices.length - 1][1];
}
}
思路:
这个区别主要是体现在递推公式上,其他都和上题一样唯一不同的地方,就是推导dp[i][0]
的时候,第i天买入股票的情况。因为股票全程只能买卖一次,所以如果买入股票,那么第i天持有股票即dp[i][0]
一定就是 -prices[i]
而本题,因为一只股票可以买卖多次,所以当第i天买入股票的时候,所持有的现金可能有之前买卖过的利润。
- 那么第i天持有股票即
dp[i][0]
,如果是第 i 天买入股票,所得现金就是昨天不持有股票的所得现金 减去 今天的股票价格 即:dp[i - 1][1] - prices[i]
。 - 那么第i天不持有股票即
dp[i][1]
,第 i 天卖出股票,所得现金就是按照今天股票佳价格卖出后所得现金即:prices[i] + dp[i - 1][0]
dp[i][0] = Math.max(dp[i - 1][0] , dp[i - 1][1] - prices[i]);//不持有的前一天或者前一天持有加上今天买入
dp[i][1] = Math.max(dp[i - 1][1] , dp[i - 1][0] + prices[i]);//持有的前一天或者前一天不持有加上今天卖出
24.3买卖股票的最佳时机Ⅲ
最多完成两笔交易
class Solution {
public int maxProfit(int[] prices) {
int[] dp = new int[4];
// 存储两次交易的状态就行了
// dp[0]代表第一次交易的买入
dp[0] = -prices[0];
// dp[1]代表第一次交易的卖出
dp[1] = 0;
// dp[2]代表第二次交易的买入
dp[2] = -prices[0];
// dp[3]代表第二次交易的卖出
dp[3] = 0;
for(int i = 1; i <= prices.length; i++){
// 要么保持不变,要么没有就买,有了就卖
dp[0] = Math.max(dp[0], -prices[i-1]);
dp[1] = Math.max(dp[1], dp[0]+prices[i-1]);
// 这已经是第二次交易了,所以得加上前一次交易卖出去的收获
dp[2] = Math.max(dp[2], dp[1]-prices[i-1]);
dp[3] = Math.max(dp[3], dp[2]+ prices[i-1]);
}
return dp[3];
}
}
思路:定义四种状态,分别是①第一次买入②第一次卖出③第二次买入④第二次卖出
- 确定dp数组中的含义:一天一共就有四个状态,①第一次买入②第一次卖出③第二次买入④第二次卖出
dp[i]
中 i 表示当前天数交易的状态 - 确定递推公式:要么保持不变,要么没有就买,有了就卖;第二次交易时,得加上前一次交易卖出去的收获
- dp数组初始化:
dp[0]
代表第一次交易的买入:dp[0] = -prices[0];
dp[1]
代表第一次交易的卖出dp[1] = 0;
dp[2]
代表第二次交易的买入dp[2] = -prices[0];
dp[3]
代表第二次交易的卖出dp[3] = 0;
- 确定遍历顺序:从前到后的
24.4最佳买卖股票时机含冷冻期
两笔交易,但存在一天的冷冻期
class Solution {
public int maxProfit(int[] prices) {
if (prices == null || prices.length < 2) {
return 0;
}
int[][] dp = new int[prices.length][2];
// bad case
dp[0][0] = 0;
dp[0][1] = -prices[0];
dp[1][0] = Math.max(dp[0][0], dp[0][1] + prices[1]);
dp[1][1] = Math.max(dp[0][1], -prices[1]);
for (int i = 2; i < prices.length; i++) {
// dp公式
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] + prices[i]);
dp[i][1] = Math.max(dp[i - 1][1], dp[i - 2][0] - prices[i]);
}
return dp[prices.length - 1][0];
}
}
思路:根据状态来解决,分为以下四种情况:
①买入股票状态
②两天前就卖出了股票,度过了冷冻期
③今天卖出股票,但是度过了冷冻期
④今天为冷冻期状态
- 确定dp数组中的含义:
dp[i][j]
,第 i 天状态为 j,所剩的最多现金为dp[i][j]
。 - 确定递推公式:
状态一dp[i][0]
,有两个具体操作:
前一天就是持有股票状态(状态一)dp[i][0] = dp[i - 1][0]
今天买入了,有两种情况:
①前一天是冷冻期(状态四)dp[i - 1][3] - prices[i]
②前一天是保持卖出股票状态(状态二)dp[i - 1][1] - prices[i]
取三种情况最大值
状态二dp[i][1]
,有两个具体操作:
前一天就是状态二 或者 前一天是冷冻期(状态四)dp[i][1] = max(dp[i - 1][1], dp[i - 1][3]);
状态三dp[i][2]
昨天一定是买入股票状态(状态一),今天卖出dp[i][2] = dp[i - 1][0] + prices[i];
状态四dp[i][3]
昨天卖出了股票(状态三)p[i][3] = dp[i - 1][2];
- dp数组初始化:
状态一dp[0][0] = -prices[0]
,买入股票所省现金为负数
状态二:第0天没有卖出dp[0][1]
初始化为0就行
状态三,状态四同样dp[0][2]; dp[0][3]
初始化为0,因为最少收益就是0,绝不会是负数。 - 确定遍历顺序:从递归公式上可以看出,
dp[i]
依赖于dp[i-1]
,所以是从前向后遍历。
最后结果取 状态二,状态三,和状态四的最大值,状态四是冷冻期,最后一天如果是冷冻期也可能是最大值。
24.5买卖股票的最佳时机含手续费
可以无数笔交易,每笔交易支付手续费
class Solution {
public int maxProfit(int[] prices, int fee) {
int len = prices.length;
// 0 : 持股(买入)
// 1 : 不持股(售出)
// dp 定义第i天持股/不持股 所得最多现金
int[][] dp = new int[len][2];
dp[0][0] = -prices[0];
for (int i = 1; i < len; i++) {
dp[i][0] = Math.max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
dp[i][1] = Math.max(dp[i - 1][0] + prices[i] - fee, dp[i - 1][1]);
}
return Math.max(dp[len - 1][0], dp[len - 1][1]);
}
}
思路:在卖出时支付手续费
- 确定dp数组中的含义:dp数组的含义:
dp[i][0]
表示第i天持有股票所省最多现金。dp[i][1]
表示第i天不持有股票所得最多现金 - 确定递推公式:
①dp[i][0]
第 i-1 天就持有股票,那么就保持现状,所得现金就是昨天持有股票的所得现金 即:dp[i - 1][0]
第 i 天买入股票,所得现金就是昨天不持有股票的所得现金减去 今天的股票价格 即:dp[i - 1][1] - prices[i]
所以:dp[i][0] = max(dp[i - 1][0], dp[i - 1][1] - prices[i]);
②dp[i][1]
第i-1天就不持有股票,那么就保持现状,所得现金就是昨天不持有股票的所得现金 即:dp[i - 1][1]
第i天卖出股票,所得现金就是按照今天股票价格卖出后所得现金,注意这里需要有手续费了即:dp[i - 1][0] + prices[i] - fee
- dp数组初始化:
dp[0][0] = -prices[0];
- 确定遍历顺序:从前往后
股票问题总结
25.最长递增子序列
给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。
class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp,1);
for (int i = 0; i < nums.length; i++) {
for (int j = 0; j < i; j++) {
if(nums[i] > nums[j])dp[i] = Math.max(dp[i],dp[j] + 1);
}
}
int res = 0;
for (int i = 0; i < dp.length; i++) {
res = Math.max(res,dp[i]);
}
return res;
}
}
思路:
- 确定dp数组中的含义:
dp[i]
表示 i 之前包括 i 的最长上升子序列的长度。 - 确定递推公式:位置 i 的最长升序子序列等于 j 从0到 i-1各个位置的最长升序子序列 + 1 的最大值
if (nums[i] > nums[j]) dp[i] = max(dp[i], dp[j] + 1);
- dp数组初始化:每一个 i,对应的
dp[i]
(即最长上升子序列)起始大小至少都是是1 - 确定遍历顺序:j 是根据 i 来的,i 要从头开始,j在i的内层
- 打印推导dp数组:
26.最长连续递增序列
给定一个未经排序的整数数组,找到最长且 连续递增的子序列,并返回该序列的长度。
贪心不解释😏
class Solution {
public int findLengthOfLCIS(int[] nums) {
int res = 1;
int count = 1;
for (int i = 1; i < nums.length; i++) {
if(nums[i] > nums[i - 1]){
count++;
res = Math.max(res,count);
}else {
count = 1;
}
}
return res;
}
}
动态规划😏:
class Solution {
public int findLengthOfLCIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp,1);
int res = 1;
for (int i = 1; i < dp.length; i++) {
if(nums[i] > nums[i - 1]){
dp[i] = dp[i - 1] + 1;
}
res = res > dp[i] ? res : dp[i];
}
return res;
}
}
思路:
- 确定dp数组中的含义:
dp[i]
代表当前下表最大连续值 - 确定递推公式:如果当前值比前一值大的条件下,当前索引(状态)处的最大连续值,比前一状态的最大连续值大1。即
dp[i] = dp[i - 1] + 1;
- dp数组初始化:数组每一处的最大连续值都是1
- 确定遍历顺序:从前往后推
- 打印推导dp数组:
27. 最长重复子数组
哎牛批😏给两个整数数组 A 和 B ,返回两个数组中公共的、长度最长的子数组的长度。
class Solution {
public int findLength(int[] nums1, int[] nums2) {
int[][] dp = new int[nums1.length][nums2.length];
for (int[] row : dp) {
Arrays.fill(row,0);
}
int res = 0;
for (int i = 0; i < nums2.length; i++) {
if(nums1[0] == nums2[i]){
dp[0][i] = 1;
res = 1;
}
}
for (int i = 0; i < nums1.length; i++) {
if(nums2[0] == nums1[i]){
dp[i][0] = 1;
res = 1;
}
}
for (int i = 1; i < nums1.length; i++) {
for (int j = 1; j < nums2.length; j++) {
if(nums1[i] == nums2[j])dp[i][j] = dp[i - 1][j - 1] + 1;
res = res > dp[i][j] ? res : dp[i][j];
}
}
return res;
}
}
思路:
- 确定dp数组中的含义:
dp[i][j]
i 指的是nums1中的索引,j 指的是nums2中的索引,指当前索引下最长的子数组 - 确定递推公式:
if(nums1[i] == nums2[j])dp[i][j] = dp[i - 1][j - 1] + 1;
如果两数组当前索引指的是同一数,那么在其左上角的状态上加一
- dp数组初始化:需要初始化第一列和第一行,如果有当前两索引指的数是相同的,就置为1,前提可以全部置为0
- 确定遍历顺序:从前到后即可
- 打印推导dp数组:
28.最长公共子序列
给定两个字符串 text1 和 text2,返回这两个字符串的最长 公共子序列 的长度。如果不存在 公共子序列 ,返回 0 。
class Solution {
public int longestCommonSubsequence(String text1, String text2) {
int[][] dp = new int[text1.length() + 1][text2.length() + 1];
for (int i = 1; i < dp.length; i++) {
for (int j = 1; j < dp[0].length ; j++) {
if(text1.charAt(i - 1) == text2.charAt(j - 1)){
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[text1.length()][text2.length()];
}
}
思路:
- 确定dp数组中的含义:
dp[i][j]
i 指的是字符串1的字符索引,j 指的是字符串2的字符索引,那么整体就是表示到当前俩索引下的最长子序列 - 确定递推公式:如果当前两索引指的字符相等那就在其左上角的数据+1,即
dp[i][j] = dp[i - 1][j - 1] + 1;
;如果不相等当前状态就取上面或者左面的最大值即可,即dp[i][j] = Math.max(dp[i - 1][j],dp[i][j - 1]);
- dp数组初始化:第一行第一列无需初始化,因为取不上字符
- 确定遍历顺序:从左上角推到右下角,自然是从前到后,起始索引都从1开始
- 打印推导dp数组:
29.不相交的线
给两个数组,把相同数连接起来,连接的线不能相交
class Solution {
public int maxUncrossedLines(int[] nums1, int[] nums2) {
int[][] dp = new int[nums1.length + 1][nums2.length + 1];
for (int i = 1; i <= nums1.length; i++) {
for (int j = 1; j <= nums2.length; j++) {
if(nums1[i - 1] == nums2[j - 1]){
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[nums1.length][nums2.length];
}
}
思路:其实就是求两个字符串的最长公共子序列的长度!
- 确定dp数组中的含义:
dp[i][j]
当前两索引下相同的数的最大个数 - 确定递推公式:相等的话取左上角的数 + 1,
dp[i][j] = dp[i - 1][j - 1] + 1;
不相等的话取上面或者左面最大的数dp[i][j] = Math.max(dp[i - 1][j],dp[i][j - 1]);
- dp数组初始化:不用管初始化
- 确定遍历顺序:从前到后
- 打印推导dp数组:以
int[] cost = {2,5,1,2,5}; int[] cost1 = {10,5,2,1,5,2};
为例
30.最大子序和
class Solution {
public int maxSubArray(int[] nums) {
int[] dp = new int[nums.length];
dp[0] = nums[0];
int res = nums[0];
for (int i = 1; i < nums.length; i++) {
dp[i] = Math.max(dp[i - 1] + nums[i],nums[i]);
res = Math.max(res,dp[i]);
}
return res;
}
}
思路:
- 确定dp数组中的含义:
dp[i]
:包括下标i之前的最大连续子序列和 - 确定递推公式:
dp[i]
只有两个方向可以推出来:
dp[i - 1] + nums[i]
,即:nums[i]
加入当前连续子序列和
nums[i]
,即:从头开始计算当前连续子序列和
即dp[i] = Math.max(dp[i - 1] + nums[i],nums[i]);
- dp数组初始化:如果只有一个数和就是这个数,所以第一个就取
nums[0];
- 确定遍历顺序:从前向后推出来的
- 打印推导dp数组:以
int[] cost1 = {-2,1,-3,4,-1,2,1,-5,4};
为例
31.判断子序列
给两个字符串,看短的字符串在长的字符串里有没有有序的都出现过
class Solution {
public boolean isSubsequence(String s, String t) {
int[][] dp = new int[s.length() + 1][t.length() + 1];
for (int i = 1; i <= s.length(); i++) {
for (int j = 1; j <= t.length(); j++) {
if (s.charAt(i - 1) == t.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + 1;
}else {
dp[i][j] = dp[i][j - 1];
}
}
}
if(dp[s.length()][t.length()] == s.length()){
return true;
}
return false;
}
}
思路:同28,最后判断结果是不是和短字符的长度相等即可
32. 不同的子序列
给一长一短字符串,判断长字符串含有多少次短字符串
class Solution {
public int numDistinct(String s, String t) {
int[][] dp = new int[s.length() + 1][t.length() + 1];
for (int i = 0; i < s.length() + 1; i++) {
dp[i][0] = 1;
}
for (int i = 1; i < s.length() + 1; i++) {
for (int j = 1; j < t.length() + 1; j++) {
if (s.charAt(i - 1) == t.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
}else{
dp[i][j] = dp[i - 1][j];
}
}
}
return dp[s.length()][t.length()];
}
}
思路:
- 确定dp数组中的含义:
dp[i][j]
:以i-1为结尾的s子序列中出现以j-1为结尾的t的个数 - 确定递推公式:①用
s[i - 1]
来匹配,那么个数为dp[i - 1][j - 1]
。②不用s[i - 1]
来匹配,个数为dp[i - 1][j]
s[i - 1]
与t[j - 1]
相等时,dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
s[i - 1]
与t[j - 1]
不相等时,dp[i][j]
只有一部分组成,不用s[i - 1]
来匹配,即:dp[i - 1][j]
- dp数组初始化:
dp[i][0]
表示:以i-1为结尾的s可以随便删除元素,出现空字符串的个数。
那么dp[i][0]
一定都是1,因为也就是把以i-1为结尾的s,删除所有元素,出现空字符串的个数就是1。
dp[0][j]
:空字符串s可以随便删除元素,出现以j-1为结尾的字符串t的个数。
那么dp[0][j]
一定都是0,s如论如何也变成不了t。
dp[0][0]
应该是1,空字符串s,可以删除0个元素,变成空字符串t。 - 确定遍历顺序:从递推公式
dp[i][j] = dp[i - 1][j - 1] + dp[i - 1][j];
和dp[i][j] = dp[i - 1][j];
中可以看出dp[i][j]
都是根据左上方和正上方推出来的。所以遍历的时候一定是从上到下,从左到右,这样保证dp[i][j]
可以根据之前计算出来的数值进行计算。
33.两个字符串的删除操作
给两个字符串,问总共要删多少次才能使两个字符串相等
class Solution {
public int minDistance(String word1, String word2) {
int[][] dp = new int[word1.length() + 1][word2.length() + 1];
for (int i = 0; i <= word1.length(); i++)dp[i][0] = i;
for (int i = 0; i <= word2.length(); i++)dp[0][i] = i;
for (int i = 1; i <= word1.length(); i++) {
for (int j = 1; j <= word2.length(); j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
}else {
dp[i][j] = Math.min(dp[i - 1][j - 1] + 2,
Math.min(dp[i - 1][j] + 1, dp[i][j - 1] + 1));
}
}
}
return dp[word1.length()][word2.length()];
}
}
思路:
- 确定dp数组中的含义:
dp[i][j]
:以i-1为结尾的字符串word1,和以j-1位结尾的字符串word2,想要达到相等,所需要删除元素的最少次数。就是说i索引遍历w1,j索引遍历w2,在当前索引下删除的次数 - 确定递推公式:
- 当
word1[i - 1]
与word2[j - 1]
相同的时候,说明不需要删除,所以和上一状态一样,dp[i][j] = dp[i - 1][j - 1];
- 当
word1[i - 1]
与word2[j - 1]
不相同的时候,可能需要w1删除,可能需要w2删除,可能都需要删除,那么取三种删除方式最小的那个,即dp[i][j] = min({dp[i - 1][j - 1] + 2, dp[i - 1][j] + 1, dp[i][j - 1] + 1});
- dp数组初始化:
dp[i][0]
:word2为空字符串,以i-1为结尾的字符串word1要删除多少个元素,才能和word2相同呢,很明显dp[i][0] = i
。一个字符串本来是空的,要跟他一样,那就得把自己删完 - 确定遍历顺序:
dp[i][j]
都是根据左上方、正上方、正左方推出来的,所以遍历的时候一定是从上到下,从左到右
34.编辑距离
给两个字符,通过插入一个字符、删除一个字符、替换一个字符操作,使得两字符相等,需要操作的次数
class Solution {
public int minDistance(String word1, String word2) {
int[][] dp = new int[word1.length() + 1][word2.length() + 1];
for (int i = 0; i <= word1.length(); i++)dp[i][0] = i;
for (int i = 0; i <= word2.length(); i++)dp[0][i] = i;
for (int i = 1; i <= word1.length(); i++) {
for (int j = 1; j <= word2.length(); j++) {
if (word1.charAt(i - 1) == word2.charAt(j - 1)) {
dp[i][j] = dp[i - 1][j - 1];
}else {
dp[i][j] = Math.min(dp[i - 1][j - 1] + 1,
Math.min(dp[i - 1][j], dp[i][j - 1])) + 1;
}
}
}
return dp[word1.length()][word2.length()];
}
}
思路:本题看着难,其实和上一题差不多,在递归公式处有所不同,如果当前两字符相等,就不需要操作,参照前一状态;如果不相等,可以进行三种操作:插删换,插入一个字符和删除的操作数一样,所以可以看作是dp[i - 1][j] + 1
或者dp[i][j - 1] + 1
而替换是需要改动i - 1、j - 1索引两个字符所以操作数得+2,且都是之前状态的,故递推公式是dp[i][j] = Math.min(dp[i - 1][j - 1],Math.min(dp[i - 1][j], dp[i][j - 1])) + 1;
35.最长回文子串
给你一个字符串 s ,找出其中最长的回文子序列,并返回该序列的长度。
class Solution {
public int longestPalindromeSubseq(String s) {
int len = s.length();
int[][] dp = new int[len + 1][len + 1];
for (int i = len - 1; i >= 0; i--) { // 从后往前遍历 保证情况不漏
dp[i][i] = 1; // 初始化
for (int j = i + 1; j < len; j++) {
if (s.charAt(i) == s.charAt(j)) {
dp[i][j] = dp[i + 1][j - 1] + 2;
} else {
dp[i][j] = Math.max(dp[i + 1][j], Math.max(dp[i][j], dp[i][j - 1]));
}
}
}
return dp[0][len - 1];
}
}
思路:
- 确定dp数组中的含义:
dp[i][j]
:字符串s在[i, j]范围内最长的回文子序列的长度 - 确定递推公式:加入s[j]的回文子序列长度为
dp[i + 1][j]
,加入s[i]
的回文子序列长度为dp[i][j - 1]
,那么dp[i][j]
一定是取最大的,即:dp[i][j] = max(dp[i + 1][j], dp[i][j - 1]);
如果s[i]
与s[j]
相同,那么dp[i][j] = dp[i + 1][j - 1] + 2;
- dp数组初始化:递推公式是计算不到 i 和j相同时候的情况。所以
i == j
时,一个字符的回文子序列长度就是1。 - 确定遍历顺序:从矩阵的角度来说,
dp[i][j]
下一行的数据。 所以遍历i的时候一定要从下到上遍历,这样才能保证,下一行的数据是经过计算的。
动态规划大总结
- 动态规划更多的就是使用状态转移容器来记录状态的变化
- 所以求组合类问题的公式(求装满背包有几种方法,填满多大容积的包)一般公式都是:
dp[j] += dp[j - nums[i]]
。且组合问题的初始化一般都为dp[0] = 1;
- 01背包问题中,物品的价值一般是数据的个数,物品的重量一般是数据的大小
- 如果求组合数就是外层for循环遍历物品,内层for遍历背包,然后内层的起始索引就是每个物品的质量,去塞背包
如果求排列数就是外层for遍历背包,内层for循环遍历物品。注意排列问题,在写递推公式时,注意不要指针越界 - 求一维
dp[i // j]
,取决于背包所在的循环索引是 i 还是 j - 定义dp数组时,dp一般表示的是当前数据的状态,所以为了不用考虑边界情况,dp维度的定义要比原始数据 + 1