动态规划之子序列问题解题模板 :: labuladong的算法小抄 #1076
Replies: 13 comments 2 replies
-
打卡,感谢楼主! |
Beta Was this translation helpful? Give feedback.
-
自顶向下的带备忘录的递归解法。 dp 函数的定义是:在子串 s[i..j] 中,最长回文子序列的长度为 dp(s, i, j)。 Java版 class Solution {
/**
* 动态规划解题套路框架。
* 提供我总结的一个思维框架,辅助你思考状态转移方程:
* 明确 base case -> 明确「状态」 -> 明确「选择」 -> 定义 dp 数组/函数的含义。
*
* 基本思路
* 1、确定 base case。
* 2、确定「状态」,也就是原问题和子问题中会变化的变量。
* 3、确定「选择」,也就是导致「状态」产生变化的行为。
* 4、明确 dp 函数/数组的定义。
*
* 自顶向下的带备忘录的递归解法
*/
public int longestPalindromeSubseq(String s) {
int n = s.length();
// 备忘录,初始化为 -1
memo = new int[n][n];
for (int[] row : memo) {
Arrays.fill(row, -1);
}
// 整个 s 的最长回文子序列长度
return dp(s, 0, n - 1);
}
// 备忘录
int[][] memo;
// dp 函数的定义是:在子串 s[i..j] 中,最长回文子序列的长度为 dp(s, i, j)。
int dp(String s, int i, int j) {
int n = s.length();
if (i < 0 || i >= n || j < 0 || j >= n) {
return 0;
}
// 查备忘录,防止重复计算
if (memo[i][j] != -1) {
return memo[i][j];
}
// 状态转移方程
if (s.charAt(i) == s.charAt(j)) {
memo[i][j] = dp(s, i + 1, j - 1) + 1;
} else {
memo[i][j] = Math.max(
dp(s, i + 1, j),
dp(s, i, j - 1)
);
}
return memo[i][j];
}
} |
Beta Was this translation helpful? Give feedback.
-
可以转化为求和自己倒序序列的最长子序列 |
Beta Was this translation helpful? Give feedback.
-
麻烦问一下 备忘录解法 为什么用的是 dp(s, i + 1, j - 1) + 1; 而不是 dp(s, i + 1, j - 1) + 2 |
Beta Was this translation helpful? Give feedback.
-
贴一下我的理解 python版本 class Solution:
def longestPalindromeSubseq(self, s: str) -> int:
if not s:
return 0
length = len(s)
# 备忘录
# 字符串s在[i, j]范围内最长的回文子序列的长度为memo[i][j]
memo = [[0] * length for _ in range(length)]
def dp(i, j):
if (i<0) or (j<0) or (i>=length) or (j>=length) or (i>j):
# 越界情况
return 0
if i == j:
return 1
if memo[i][j]:
# 备忘录中有结果的话 使用备忘录
return memo[i][j]
# 开始条件判断
if s[i] == s[j]:
# 可以这么理解
# 假设有索引 i i+1 i+2 ..... j-1 j
# 如果s[i] == s[j], 那么 memo[i][j] = memo[i+1][j-1] + 2
memo[i][j] = dp(i+1, j-1) + 2
else:
memo[i][j] = max(
# 还可以这么理解
# 假设有索引 i i+1 i+2 ..... j-1 j
# 如果 s[i] != s[j], 那么 memo[i][j] = max(memo[i][j-1], memo[i+1][j])
# 加入s[i]
dp(i, j-1),
# 加入s[j]
dp(i+1, j)
)
return memo[i][j]
return dp(0, length-1) |
Beta Was this translation helpful? Give feedback.
-
对啊为什么dp函数和dp数组,当i,j相同时一个加1 一个加2啊 |
Beta Was this translation helpful? Give feedback.
-
转化为传统的 i j 的下标, 这样判断回文比较直观 class Solution {
public int longestPalindromeSubseq(String s) {
int n = s.length();
int[][] dp = new int[n][n];//dp[i][j] s[i..j] 中最长回文子序列的长度
for(int i = 0; i < n; i++){
dp[i][i] = 1;
}
for(int len = 2 ; len<= n; len++){
for(int i = 0; i+len - 1 < n ; i++){
int j = i + len -1;
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], dp[i][j-1]);
}
}
}
return dp[0][n-1];
}
} |
Beta Was this translation helpful? Give feedback.
-
让字符串成为回文串的最少插入次数,贴一个精简的从顶向下的C++。跟着东哥做了不少DP,感觉还是从顶向下带个备忘录更简单一点,有一些图的问题从base case出发自底向上更好 class Solution {
vector<vector<int>> memo;
public:
// 返回s[i, j] 变成回文串的最少插入次数
int dp(string& s, int i, int j) {
// base case
if (i >= j) return 0;
if (memo[i][j] != -1) return memo[i][j];
if (s[i] == s[j]) return memo[i][j] = dp(s, i + 1, j - 1);
else return memo[i][j] = min(dp(s, i + 1, j), dp(s, i, j - 1)) + 1;
}
int minInsertions(string s) {
int n = s.size();
memo.assign(n, vector<int> (n, -1));
return dp(s, 0, n - 1);
}
}; |
Beta Was this translation helpful? Give feedback.
-
楼上那位朋友这里这里加的是1 if (s.charAt(i) == s.charAt(j)) {
memo[i][j] = dp(s, i + 1, j - 1) + 1; 这是因为他的base case设置的问题: int n = s.length();
if (i < 0 || i >= n || j < 0 || j >= n) {
return 0;
} 实际上 int n = s.length();
if (i >= n || j < 0) {
return 0;
} 当 class Solution {
public int longestPalindromeSubseq(String s) {
int n = s.length();
memo = new int[n][n];
for (int[] row : memo) {
Arrays.fill(row, -1);
}
// 在这里将结果除以2
return dp(s, 0, n - 1) / 2;
}
int[][] memo;
int dp(String s, int i, int j) {
int n = s.length();
if (i >= n || j < 0) {
return 0;
}
if (memo[i][j] != -1) {
return memo[i][j];
}
if (s.charAt(i) == s.charAt(j)) {
memo[i][j] = dp(s, i + 1, j - 1) + 2;
} else {
memo[i][j] = Math.max(
dp(s, i + 1, j),
dp(s, i, j - 1)
);
}
// 不能在这里将结果除以2
return memo[i][j];
}
} 但是实际上就像我上面说的,这个算法相当于扫了两边字符串,其实当 class Solution {
public int longestPalindromeSubseq(String s) {
memo = new int[s.length()][s.length()];
for (int[] ints : memo) {
Arrays.fill(ints, Integer.MIN_VALUE);
}
return process(s.toCharArray(), 0, s.length() - 1);
}
int[][] memo;
public int process(char[] s, int left, int right) {
if (left > right) {
return 0;
} else if (left == right) {
return 1;
}
if (memo[left][right] != Integer.MIN_VALUE) {
return memo[left][right];
}
if (s[left] == s[right]) {
memo[left][right] = process(s, left + 1, right - 1) + 2;
} else {
memo[left][right] = Math.max(
process(s, left + 1, right),
process(s, left, right - 1)
);
}
return memo[left][right];
}
}
我自己在刚开始看这篇文章时,东哥没有给出备忘录解法,所以来看评论区,这里是加一,东哥那里是加二,导致我自己懵逼了很久,希望我这里能够帮到大家,使后来者不需要再踩坑。当然由于我水平有限可能会有一些错误,也希望大家能够指出 最后也想请教,这道题目的写法是否能够从中间往两侧扩?上面的写法都是从两侧往中间缩,自己水平有限尝试了很久也没写出来 |
Beta Was this translation helpful? Give feedback.
-
1312 java dp 压缩版 class Solution {
public int minInsertions(String s) {
int n = s.length();
int[] dp = new int[n];
// int[][] dp = new int[n][n];
// for(int i = 0; i < n; i++) {
// dp[i] = 1;
// // dp[i][i] = 1;
// }
for (int i = n-1; i >= 0; i--) {
int pre = 0;
for ( int j = i+1; j <n ; j++) {
int temp = dp[j];
if(s.charAt(i) == s.charAt(j)) {
dp[j] = pre;
// dp[i][j] = dp[i+1][j-1] +2;
} else {
dp[j] = Math.min(dp[j], dp[j-1])+1;
// dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1]);
}
pre = temp;
}
}
return dp[n-1];
}
} |
Beta Was this translation helpful? Give feedback.
-
class Solution:
def minInsertions(self, s: str) -> int:
# 定义dp[i][j]为s[0..n-1]的最长回文长度
n = len(s)
dp = [[0]*n for _ in range(n)]
# base case
for i in range(n):
dp[i][i] = 1
# 反向遍历,保证已知状态已经被求出
for i in range(n-1, -1, -1):
for j in range(i+1, n):
if s[i] == s[j]:
dp[i][j] = dp[i+1][j-1] + 2
else:
dp[i][j] = max(dp[i+1][j], dp[i][j-1])
return n - dp[0][n-1] |
Beta Was this translation helpful? Give feedback.
-
打卡,最后那个把求最少插入次数变为回文串,一转为s长度-最长子序列长度,这脑筋太能急转弯了666 |
Beta Was this translation helpful? Give feedback.
-
提问: 让字符串成为回文串的最少插入次数的算法中。dp[i][j]=min(dp[i][j-1],dp[i+1[j])+1 为什么要加一啊,不太懂啊 |
Beta Was this translation helpful? Give feedback.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
文章链接点这里:动态规划之子序列问题解题模板
评论礼仪 见这里,违者直接拉黑。
Beta Was this translation helpful? Give feedback.
All reactions