给出一个字符串s,分割s使得分割出的每一个子串都是回文串计算将字符串s分割成回文分割结果的最小切割数例如:给定字符串s=“aab”,返回1,因为回文分割结果[“aa”,“b”]是切割一次生成的。
“aab”
将字符串s分割成回文分割结果的最小切割数
字符串前i个字符的最小分割分割的段数,也就是最小分割数+1
F[0] = 1–>“a”
F[1] = is(str[j, 1])?(j>0?F[j-1]+1:1) : F[1-1]+1 = 1–>“a” “a” || “aa” (0<=j<=i)
F[2] = is(str[j, 2])?(j>0?F[j-1]+1:1) : F[2-1]+1 = 2–>“aa” “b” || “aab” (0<=j<=i)
F[0] = 1–>“a”
F[1] = is(str[j, 1])?(j>0?F[j-1]+1:1) : F[1-1]+1 = 2–>“ab” “b”(0<=j<=i)
F[2] = is(str[j, 2])?(j>0?F[j-1]+1:1) : F[2-1]+1 = 2–>“abb” “bb” “b” (0<=j<=i)
F[3] = is(str[j, 3])?(j>0?F[j-1]+1:1) : F[3-1]+1 = 1–>“abba” “bba” “ba” “a” (0<=j<=i)
F[4] = is(str[j, 4])?(j>0?F[j-1]+1:1) : F[4-1]+1 = 3–>“abbab” “bbab” “bab” “ab” “b” (0<=j<=i)
F[4]漏掉了这些状态:“abbab” “abba” “abb” “ab” “a” (0<=j<=i)
F[4-1]+1就是"abba"+"b"分割方式
上面状态不完美的原因是忽略了部分子状态,因此重新找状态:
F[4]: if is(str[0, j]) F[i] = (j>0?F[j-1]+1:1) > (i>0?F[i-1]+1:1)? F[i-1]+1 : (j>0?F[j-1]+1:1);break; (0<=j<=i)
(j>0?F[j-1]+1:1)就是"abbab" “bbab” “bab” “ab” "b"情况
(i>0?F[i-1]+1:1)就是"abbab" “abba” “abb” “ab” "a"情况
not prefect:F[i] = is(str[j, i])? (j>0?F[j-1]+1:1) : F[i-1]+1 (0<=j<=i)
prefect:F[i] = (j>0?F[j-1]+1:1) > (i>0?F[i-1]+1:1)? F[i-1]+1 : (j>0?F[j-1]+1:1);
F[0] = 1
F[s.length - 1] - 1
Java
import java.util.*;
public class Solution {
/**
*
* @param s string字符串
* @return int整型
*/
public int minCut (String s) {
int len = s.length();
int F[] = new int[len];
//F[0] = 1
//F[i] = is(str[j, i])? (j>0?F[j-1]+1:1) : F[i-1]+1 (0<=j<=i)
for(int i=0; i0?F[j-1]+1:1): F[i-1]+1;
if(isPalindrom(s, j, i))break;
}
}
return F[len-1]-1;
}
boolean isPalindrom(String s, int left, int right){
while(left < right){
if(s.charAt(left++) != s.charAt(right--))
return false;
}
return true;
}
}
C++
class Solution {
public:
/**
*
* @param s string字符串
* @return int整型
*/
int minCut(string s) {
// write code here
//F[i] = is(str[j, i])?F[i-1]:F[i-1]+1 (0<=j<=i)
int len = s.length();
int* F = new int[len];
//F[0] = 1
//F[i] = is(str[j, i])? (j>0?F[j-1]+1:1) : F[i-1]+1 (0<=j<=i)
for(int i=0; i0?F[j-1]+1:1): F[i-1]+1;
if(isPalindrom(s, j, i))break;
}
}
return F[len-1]-1;
}
bool isPalindrom(string s, int left, int right){
while(left < right){
if(s[left++] != s[right--])
return false;
}
return true;
}
};
Java
import java.util.*;
public class Solution {
/**
*
* @param s string字符串
* @return int整型
*/
public int minCut (String s) {
int len = s.length();
int F[] = new int[len];
//F[0] = 1
//F[i] = is(str[j, i])? (j>0?F[j-1]+1:1) : F[i-1]+1 (0<=j<=i)
for(int i=0; i0?F[j-1]+1:1) > F[i-1]+1? F[i-1]+1 : (j>0?F[j-1]+1:1)
if(isPalindrom(s, j, i)){
F[i] = (j>0?F[j-1]+1:1) > (i>0?F[i-1]+1:1)? F[i-1]+1 : (j>0?F[j-1]+1:1);
break;
}
}
}
return F[len-1]-1;
}
boolean isPalindrom(String s, int left, int right){
while(left < right){
if(s.charAt(left++) != s.charAt(right--))
return false;
}
return true;
}
}
C++
class Solution {
public:
/**
*
* @param s string字符串
* @return int整型
*/
int minCut(string s) {
// write code here
//F[i] = is(str[j, i])?F[i-1]:F[i-1]+1 (0<=j<=i)
int len = s.length();
int* F = new int[len];
//F[0] = 1
//F[i] = is(str[j, i])? (j>0?F[j-1]+1:1) : F[i-1]+1 (0<=j<=i)
for(int i=0; i0?F[j-1]+1:1) > (i>0?F[i-1]+1:1)? F[i-1]+1 : (j>0?F[j-1]+1:1);
break;
}
}
}
return F[len-1]-1;
}
bool isPalindrom(string s, int left, int right){
while(left < right){
if(s[left++] != s[right--])
return false;
}
return true;
}
};
但对于极度扣性能的我来说,一定可以找到更舒服的状态和定义更完美的转移方程
字符串前i个字符的最小分割分割次数
F[0] = 0 --> “a”
F[2] = 1 --> “a” “b” (“ab” | “a” “b”)
F[3] = 1 --> “a” “bb” (“abb” | “ab” “b” | “a” “b” “b” | “a” “bb” | “ab” “b”)
…
每个状态只需要关注两种方向的组合就行了
F[i-1]+1对应[0, i]的组合(0<=i<=s.len)
F[j-1]+1对应[j, i]的组合(j=0;j<=i;++j)
执行逻辑:
F[i] = if isPalindrom(str[j,i]) min((j>0?F[j-1]+1:1), (i>0?F[i-1]+1:1)) (j=0;j<=i;++j)(0<=i<=s.len)
if isPalindrom(str[j,i]) F[i] = min((j>0?F[j-1]+1:0), (i>0?F[i-1]+1:0)); (j=0;j<=i;++j)(0<=i<=s.len)
F[s.len-1]
Java
import java.util.*;
public class Solution {
/**
*
* @param s string字符串
* @return int整型
*/
public int minCut (String s) {
int len = s.length();
int F[] = new int[len];
//F[0] = 1
//F[i] = is(str[j, i])? (j>0?F[j-1]+1:1) : F[i-1]+1 (0<=j<=i)
for(int i=0; i0?F[j-1]+1:0), (i>0?F[i-1]+1:0));
break;
}
}
}
return F[len-1];
}
boolean isPalindrom(String s, int left, int right){
while(left < right){
if(s.charAt(left++) != s.charAt(right--))
return false;
}
return true;
}
}
C++
class Solution {
public:
/**
*
* @param s string字符串
* @return int整型
*/
int minCut(string s) {
// write code here
//F[i] = is(str[j, i])?F[i-1]:F[i-1]+1 (0<=j<=i)
int len = s.length();
int* F = new int[len];
//F[0] = 1
//F[i] = is(str[j, i])? (j>0?F[j-1]+1:1) : F[i-1]+1 (0<=j<=i)
for(int i=0; i0?F[j-1]+1:0), (i>0?F[i-1]+1:0));
break;
}
}
}
return F[len-1];
}
bool isPalindrom(string s, int left, int right){
while(left < right){
if(s[left++] != s[right--])
return false;
}
return true;
}
};
C++ string lenght()和size()成员方法都可以求字符串长度
内存和运行时间都有所提高
将字符串s分割成回文分割结果的最小切割数
字符串s前i个字符分割成回文分割结果的最小切割数
F[1] = “a” --> 0
F[2] = “aa” --> 0
F[3] = “aab” F[2]+1 --> 1
F[3]可以用上F[2]的状态
F[1] = “a” --> 0
F[2] = “aa” --> 0
F[3] = “aab”, F[1]|“ab”, F[2]|“b” --> 1
F[4] = “aaba”, F[1]|“aba”, F[2]|“ba”, F[3]|“a”
F[5] = “aabaa” --> 0
F[6] = “aabaab”, F[2]|“baab”, F[5]|“a” --> 1
F[1] = “a” --> 0
F[2] = “ab” --> 1
F[3] = “abb”, F[1]|“bb” 1, F[2]|“b” 2 --> 1
F[4] = “abba” --> 0
F[5] = “abbab”, F[4]|“b” --> 1
F[i]: j
初始状态
全给最大值
F[i] = i-1 (i)
F[n]
class Solution {
public:
/**
*
* @param s string字符串
* @return int整型
*/
int minCut(string s) {
vector minc(s.length()+1);
//F[i] = i-1
for(int i=1; i<=s.length(); ++i)
minc[i] = i-1;
for(int i=2; i<=s.length(); ++i){
//isPalindrom(str[1,i]) 前i个字符是回文
if(isPalindrom(s, 0, i-1)){
minc[i] = 0;
continue;
}
//isPalindrom(str[j,i]) j到i字符是回文,str[0,i]就是[0,j]+1
for(int j=1; j
F[0]给-1,就可以将j==0合并到循环里
class Solution {
public:
/**
*
* @param s string字符串
* @return int整型
*/
int minCut(string s) {
vector minc(s.length()+1);
//F[i] = i-1
for(int i=0; i<=s.length(); ++i)
minc[i] = i-1;
//F[0]给-1,就可以将j==0合并到循环里
for(int i=2; i<=s.length(); ++i){
//isPalindrom(str[1,i]) 前i个字符是回文
//isPalindrom(str[j,i]) j到i字符是回文,str[0,i]就是[0,j]+1
for(int j=0; j
继续对判断字符串是不是回文的函数进行动规优化–>整个字符串任意区间是否是回文结果保存在二维矩阵中,判断分割数的循环中再判断一个区间是不是回文时间复杂度就是O(1)了
字符串区间[i, j]是否是回文串
区间[i, j]是否是回文串
F[i, j]: [j+1, j-1] && s[i] == s[j] (j>=i状态才有意义)
j
j-i == 1 return s[i] == s[j];
简单举个例子
F[3,4]就依赖于F[4,3]的状态,也就是当前的状态依赖于二维矩阵中左下角的状态,j>=i状态才有意义则二维矩阵从定义得到所有状态的结果为之都保持上三角矩阵,我们需要定义n^2的矩阵(n是字符串长度)
class Solution {
public:
/**
*
* @param s string字符串
* @return int整型
*/
int minCut(string s) {
vector> ispal(s.length(), vector(s.length(), 0));
ispal[0][0] = 1;
for(int i=s.length()-1; i>=0; --i){
for(int j=i; j minc(s.length()+1);
//F[i] = i-1
for(int i=0; i<=s.length(); ++i)
minc[i] = i-1;
//F[0]给-1,就可以将j==0合并到循环里
for(int i=2; i<=s.length(); ++i){
//isPalindrom(str[1,i]) 前i个字符是回文
//isPalindrom(str[j,i]) j到i字符是回文,str[0,i]就是[0,j]+1
for(int j=0; j
给定两个单词word1和word2,请计算将word1转换为word2至少需要多少步操作。
你可以对一个单词执行以下3种操作:
( a) 在单词中插入一个字符
( b) 删除单词中的一个字符
( c) 替换单词中的一个字符
word1转成word2的最小操作次数(编辑数)
"ab"转成"bc"有三种办法:
(1).删除’a’–>“b”;插入’c’–>“bc”
(2).‘a’替换成’b’–>“bb”;‘b’替换成’c’–>“bc”
(3).‘a’替换成’’–>“b”;插入’c’–>“bc”
word1前i个字符转成word2的最小操作次数
F(i, j): word1前i个字符转成word2的前j个字符的最小操作次数
F(i): word1前i个字符转成word2的最小操作次数
F(i):F(i-1)+1 (删除第i个字符)
F(1):最好的情况是strlen(word2)-1,最差是strlen(word2)
这个状态只考虑了删除,重新考虑:...如下:
当前的状态要考虑能够使用到前面已知的状态,当前word1前i个字符转成word2的前j个字符的状态可以通过:word1的前i-1个字符转成word2的前j-1个字符的状态加可能需要的一次替换1得到; word1的前i个字符转成word2的前j-1个字符的状态加一次插入2得到; word1的前i-1个字符转成word2的前j个字符的状态加一次删除3得到; 三种前面已知的状态得到,因此当前状态word1前i个字符转成word2的前j个字符的值就取自三者中最小的
F(1, 1): “a” --> “b” 发生一次替换 = 1
F(2, 1): “ab” --> “b” F(1, 1)+删除’b’ = 2
F(1, 2): “a” --> “bc” F(1, 1)+插入’c’ = 2
F(2, 2): “ab” --> “bc” min{F(1, 1):“bb” + ‘b’替换成’c’ = 2; F(2, 1):“b” + 插入’c’ = 3; F(1, 2):“bcb” + 'b’删除 = 3} = 2
F(2, 2)可以通过:F(1, 1)加一次替换; F(2, 1)加一次插入; F(1, 2)加一次删除得到,取三者最小值即可
状态有两个参数,很明显所有的状态需要二维矩阵才能表示完
下标 | j | 0 | 1 | 2 |
---|---|---|---|---|
i | s[i/j] | “” | b | c |
0 | “” | 状态:F(0, 0) 始末:""–>"" 操作:无 编辑数:0 |
状态:F(0, 1) 始末:""–>“b” 操作:插入"b" 编辑数:1 |
状态:F(0, 2) 始末:""–>“bc” 操作:插入"b";插入"b" 编辑数:2 |
1 | a | 状态:F(1, 0) 始末:“a”–>"" 操作:删除"a" 编辑数:1 |
状态:F(1, 1) 始末:“a”–>“b” 操作: 替换:“a”–>“b” = F(0, 0)+1 插入:""-b->“b” = F(1, 0)+1 删除:“ba”-a->“b” = F(0, 1)+1 编辑数:min{F(0, 0), F(1, 0), F(0, 1)}+1 = 1 |
状态:F(1, 2) 始末:“a”–>“bc” 操作: 替换:“ba”–>“bc” = F(0, 1)+1 插入:“b”-c->“bc” = F(1, 1)+1 删除:“bca”-a->“bc” = F(0, 2)+1 编辑数:min{F(0, 1), F(1, 1), F(0, 2)}+1 = 2 |
2 | b | 状态:F(2, 0) 始末:“ab”–>"" 操作:删除"a";删除"b" 编辑数:2 |
状态:F(2, 1) 始末:“ab”–>“b” 操作: 替换:“b”–>“b” = F(1, 0)+04 插入:“b”-c->“bc” = F(2, 0)+1 删除:“bca”-a->“bc” = F(1, 1)+1 编辑数:min{F(1, 0), F(2, 0), F(1, 1)}+1 = 1 |
状态:F(2, 2) 始末:“ab”–>“bc” 操作: 替换:“bb”–>“b” = F(1, 1)+1 插入:“b”-c->“bc” = F(2, 1)+1 删除:“bcb”-b->“bc” = F(1, 2)+1 编辑数:min{F(1, 1), F(2, 1), F(1, 1)}+1 = 1 |
F(i, j)可以通过
字符串string下标是从0开始的,因此判断word1的第i个和word2的第j个字符是否相同应该是:word1[i-1]==word2[j-1]
F(i, j):
F(0, 0) = 0
F(i, 0) = i (删除)
F(0, j) = j (插入)
F(strlen(word1), strlen(word2))
需要注意的是word1,word2只要有一个是空字符串,就直接返回非空字符串的长度
class Solution {
public:
/**
*
* @param word1 string字符串
* @param word2 string字符串
* @return int整型
*/
int minDistance(string word1, string word2) {
// write code here
int word1_len = word1.size(); //i
int word2_len = word2.size(); //j
if(!word1_len) return word2_len;
if(!word2_len) return word1_len;
vector> mid_res(word1_len+1, vector(word2_len+1, 0));
//F(i, 0) = i (删除)
for(int i=1; i<=word1_len; ++i)
mid_res[i][0] = i;
// F(0, j) = j (插入)
for(int j=1; j<=word2_len; ++j)
mid_res[0][j] = j;
for(int i=1; i<=word1_len; ++i){
for(int j=1; j<=word2_len; ++j){
mid_res[i][j] = min(mid_res[i-1][j-1]+(word1[i-1]==word2[j-1]?0:1), min(mid_res[i-1][j]+1, mid_res[i][j-1]+1));
}
}
return mid_res[word1_len][word2_len];
}
};
给定两个字符串S和T,返回S子序列等于T的不同子序列个数有多少个?
字符串的子序列是由原来的字符串删除一些字符(也可以不删除)在不改变相对位置的情况下的剩余字符(例如,"ACE"is a subsequence of"ABCDE"但是"AEC"不是)
例如:
S="nowcccoder", T = "nowccoder"
返回3
。。。。。。后续更新不同子序列(Distinct Subsequences)的解题思路和分析步骤,敬请关注+收藏
word1的前i-1已经转成word2的前j-1个,则word1的第i个字符至少一次替换就可以得到word2的第j个字符从而word1转成word2,如果word1[i]==word2[j]则不用替换 ↩︎
word1的前i个字符已经转成word2的前j-1个字符,则word1插入word2的第j个字符就可以得到word2 ↩︎
word1的前i-1个字符已经转成word2的前j个字符,则word1删除word1的第i个字符就可以得到word2 ↩︎
条件判断word1[2]==word1[1] ↩︎