算法训练营(day56)
算法训练营(day56)
动态规划理论基础
动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的。
举个例子:有N件物品和一个最多能背重量为W 的背包。第i件物品的重量是weight[i],得到的价值是value[i] 。每件物品只能用一次,求解将哪些物品装入背包里物品价值总和最大。
动态规划中 dp[j]
是由 dp[j-weight[i]]
推导出来的,然后取 max(dp[j], dp[j - weight[i]] + value[i])
。
动态规划的解题步骤
- 确定
dp
数组(dp table)以及下标的含义 - 确定递推公式
dp
数组如何初始化- 确定遍历顺序
- 举例推导
dp
数组
647. 回文子串
题目链接:https://leetcode.cn/problems/palindromic-substrings/description/
给你一个字符串 s
,请你统计并返回这个字符串中 回文子串 的数目。
回文字符串 是正着读和倒过来读一样的字符串。
子字符串 是字符串中的由连续字符组成的一个序列。
具有不同开始位置或结束位置的子串,即使是由相同的字符组成,也会被视作不同的子串。
解题思路
解题过程:动态规划
按照动规五部曲来分析:
- 确定dp数组(dp table)以及下标的含义
dp[j]表示容量为j的背包,最多可以背最大重量为dp[j]
本题的 dp[i][j]
:判断是否是回文
- 确定递推公式
s[i]
与s[j]
相同,则有三种情况- i = j,单个元素自然是回文
|j - i| = 1
,相邻的两个元素组成回文|j - i| > 1
的情况,如果此时的s[i]
与s[j]
已经相同了,要考虑s[i + 1]
与s[j - 1]
是否相同
s[i]
与s[j]
不相同,则dp[i][j] = false
所以得到的递推公式就是:
1 | if(s.charAt(i) == s.charAt(j)){ |
dp数组的初始化
dp 的初始化为 false
1 | boolean[][] dp = new boolean[len][len]; |
- 确定遍历顺序
简单理解,j 要从右往左遍历,i 要从左往右遍历,当 i = j 时,说明整个字符串遍历结束
用矩阵解释:
要从下到上,从左到右遍历,这样保证 dp[i + 1][j - 1]
都是经过计算的
1 | for(int j = 0; j < len; j++){ |
- 推导dp数组
详细代码
动态规划:
1 | class Solution { |
中心扩散:
1 | class Solution { |
516. 最长回文子序列
题目链接:https://leetcode.cn/problems/longest-palindromic-subsequence/
给你一个字符串 s
,找出其中最长的回文子序列,并返回该序列的长度。
子序列定义为:不改变剩余字符顺序的情况下,删除某些字符或者不删除任何字符形成的一个序列。
解题思路
解题过程:动态规划
按照动规五部曲来分析:
- 确定dp数组(dp table)以及下标的含义
dp[j]表示容量为j的背包,最多可以背最大重量为dp[j]
本题的 dp[i][j]
:字符串s在[i, j]范围内最长的回文子序列的长度
回文子串是要连续的,回文子序列可不是连续的
- 确定递推公式
关键逻辑就是看 s[i]
与 s[j]
是否相同
s[i]
与s[j]
相同,则dp[i][j] = dp[i + 1][j - 1] + 2
s[i]
与s[j]
不相同,则有三种情况:- 单加一个
s[i]
,则dp[i][j] = dp[i][j - 1]
- 单加一个
s[j]
,则dp[i][j] = dp[i + 1][j]
- 都没得加 ,则
dp[i][j] = dp[i + 1][j - 1]
- 单加一个
最后得到递推公式是:
1 | if (s.charAt(i) == s.charAt(j)){ |
dp数组的初始化
首先需要手动初始化
i = j
时的dp:
1 | int[][] dp = new int[len + 1][len + 1]; |
其他情况的dp初始化为0即可
- 确定遍历顺序
从递推公式可以看出,dp[i][j]
依赖于dp[i + 1][j - 1]
、 dp[i][j - 1]
和 dp[i + 1][j]
,所以遍历i的时候一定要从下到上遍历,这样才能保证下一行的数据是经过计算的,j 可以正常的从左到右。
1 | for (int i = len - 1; i >= 0; i--){ |
- 推导dp数组
详细代码
1 | class Solution { |