题目概述
题目链接:点我做题
题解
一、普通动态规划
还是那句话,字符串类的动态规划可以考虑往考虑前i个字符的反应问题的状态靠,比如本题,定义
f
(
i
)
f(i)
f(i)为考虑字符串s的前i个字符分割为回文子串的最小分割数,假设字符串s的长度是n,那么问题的答案就是
f
(
n
)
f(n)
f(n).
考虑状态转移方程:如果此时从头到底i个字符已经是回文串了,那么不需要分割,分割数是0;否则可以这样把问题分解:把串从第j个字符分开(
0
<
j
<
i
0<j<i
0<j<i),如果第j+1个字符到第i个字符构成了回文串,那么就得到了一种分割方案:把前j个字符分割为回文子串再加上第j+1个字符到第j个字符也形成了回文子串,这时分割数就等于前j个字符分割为回文子串的最小分割数
f
(
j
)
f(j)
f(j)再加上第j个字符后的那一刀分割,即
f
(
j
)
+
1
f(j)+1
f(j)+1,为了得到最小分割数,我们需要遍历j从1到i-1,选出符合条件的最小分割数。
至于如何判断回文串,可以用双指针法。
本题的边界条件,显然
f
(
0
)
=
0
,
f
(
1
)
=
0
f(0) = 0,f(1) = 0
f(0)=0,f(1)=0,0个字符不需要分割、1个字符也不用分割它自己就是回文串;其他的情况,由于我们要求的是最小值,一种方案是全都初始化为INT_MAX
,另一种方案是发现含有i个字符的区间,最糟情况是分割i-1下,使得分成i个单个字符,它们都是回文串了,因此初始条件也可以写成
f
(
i
)
=
i
−
1
f(i)=i - 1
f(i)=i−1.
代码:
class Solution {
public:
int minCut(string s)
{
/*
f(i)考虑第1个字符到第i个字符,分割为回文子串的最小分割数
状态转移方程:
如果整体是一个回文串 也就是j等于0时 第j+1=1个字符到第i个字符是回文串
则f(i)等于0
否则j从1开始,如果第j+1个字符到第i个字符为回文串
则方法数等于在第j个字符后且一刀 1加上前面到底j个字符的方法数f(j)
也就是 f(i) = f(j) + 1 j < i
所以做一个搜索 f(i) = min(check(f(j))) + 1
*/
int size = s.size();
if (check(s, 0, size - 1) == true)
{
return 0;
}
vector<int> dp(size + 1, INT_MAX);
/*
*这里初始状态也可以弄成dp[0] = 0 其他的i dp[i] = i - 1
*初始最多分割次数就是都分割成1个字符
*/
dp[0] = 0;
dp[1] = 0;
for (int i = 2; i <= size; ++i)
{
for (int j = 0; j < i; ++j)
{
if (check(s, j, i - 1) == true)
{
if (j == 0)
{
/*对应整体*/
dp[i] = 0;
break;
}
else
{
/*对应j=1开始到i-1*/
dp[i] = min(dp[i], dp[j] + 1);
}
}
}
}
return dp[size];
}
bool check(const string& s, int left, int right)
{
if (left == right)
{
return true;
}
while (left < right)
{
if (s[left] != s[right])
{
return false;
}
++left;
--right;
}
return true;
}
};
时间复杂度:
O
(
n
3
)
O(n^3)
O(n3)
空间复杂度:
O
(
n
)
O(n)
O(n)
二、记忆化搜索优化
本题还可以再优化,判断子串是否为回文串这一操作我们可以提前做好,并把状态储存在一个二维矩阵
s
t
a
t
e
state
state中,
s
t
a
t
e
[
i
]
[
j
]
state[i][j]
state[i][j]表示下标为i到下标为j的子串是否为回文串,关于判断是否为回文串,我们之前在最长回文子串的三种解法中介绍过一种动态规划的方法,即定义F(i,j)表示下标为i到下标为j的子串是否为回文子串,状态转移方程为
F
(
i
,
j
)
=
(
s
[
i
]
=
=
s
[
j
]
)
&
&
F
(
i
+
1
,
j
−
1
)
F(i,j) = (s[i]==s[j])\&\&F(i+1,j-1)
F(i,j)=(s[i]==s[j])&&F(i+1,j−1),初始状态为
F
(
i
,
i
)
=
t
r
u
e
,
F
(
i
,
i
+
1
)
=
(
s
[
i
]
=
=
s
[
i
+
1
]
)
F(i,i)=true,F(i,i+1) = (s[i]==s[i+1])
F(i,i)=true,F(i,i+1)=(s[i]==s[i+1]),这里可以单独拿出来作为一个
O
(
n
2
)
O(n^2)
O(n2)的算法放在外面把每个子串是否为回文子串先判断好咯,然后进循环直接判断
[
j
,
i
−
1
]
[j,i-1]
[j,i−1]是否为回文串是直接看
s
t
a
t
e
[
j
]
[
i
−
1
]
state[j][i-1]
state[j][i−1]即可。
针对这个状态转移方程,本人了解的有两种写法,一种是让外层的循环i从大到小遍历,内层的循环j从i到n遍历,这也就是从对角线右下角往上遍历的思路;另一种写法是控制此时判断的回文子串长度,从L=2开始遍历到L=n,因为判断长度为L的串是否为回文串需要的关系是长度为L-2的串是否为回文串,所以这样遍历也保证了可以覆盖答案。
两种实现的代码都给出:
从对角线右下角往上遍历:
class Solution {
public:
int minCut(string s)
{
/*
刚刚的算法是一个O(N^3)的算法,其实可以提前把判断(j,i-1)是否是回文串给分割出来
放到外面写 至于判断是否是回文串 可以用动态规划
f(i,j) = (s[i] == s[j]) && f(i + 1, j - 1)
f(i,i) = true; f(i, i + 1) = (s[i] == s[i + 1])
*/
int size = s.size();
auto state = getstate(s);
/*
走原本的动态规划 F(i) = 0 if state(0, i - 1) == true
else F(i) = min(F(i), F(j) + 1) j满足 state(j,i - 1) == true
*/
vector<int> dp(size + 1);
for (int i = 1; i <= size; ++i)
{
/*最初的最少分割次数就是字符数减1 即全都分割为1个字符*/
dp[i] = i - 1;
}
for (int i = 2; i <= size; ++i)
{
for (int j = 0; j < i; ++j)
{
if (state[j][i - 1])
{
if (j == 0)
{
dp[i] = 0;
break;
}
else
{
dp[i] = min(dp[i], dp[j] + 1);
}
}
}
}
return dp[size];
}
vector<vector<bool>> getstate(const string& s)
{
int n = s.size();
vector<vector<bool>> ret(n, vector<bool>(n));
for (int i = n - 1; i >= 0; --i)
{
for (int j = i; j < n; ++j)
{
if (i == j)
{
ret[i][j] = true;
}
else if (j == i + 1)
{
ret[i][j] = (s[i] == s[j]);
}
else
{
ret[i][j] = (s[i] == s[j]) && ret[i + 1][j - 1];
}
}
}
return ret;
}
};
控制每轮循环判断的回文串长度:
class Solution {
public:
int minCut(string s)
{
/*
刚刚的算法是一个O(N^3)的算法,其实可以提前把判断(j,i-1)是否是回文串给分割出来
放到外面写 至于判断是否是回文串 可以用动态规划
f(i,j) = (s[i] == s[j]) && f(i + 1, j - 1)
f(i,i) = true; f(i, i + 1) = (s[i] == s[i + 1])
*/
int size = s.size();
auto state = getstate(s);
/*
走原本的动态规划 F(i) = 0 if state(0, i - 1) == true
else F(i) = min(F(i), F(j) + 1) j满足 state(j,i - 1) == true
*/
vector<int> dp(size + 1);
for (int i = 1; i <= size; ++i)
{
/*最初的最少分割次数就是字符数减1 即全都分割为1个字符*/
dp[i] = i - 1;
}
for (int i = 2; i <= size; ++i)
{
for (int j = 0; j < i; ++j)
{
if (state[j][i - 1])
{
if (j == 0)
{
dp[i] = 0;
break;
}
else
{
dp[i] = min(dp[i], dp[j] + 1);
}
}
}
}
return dp[size];
}
vector<vector<bool>> getstate(const string& s)
{
int n = s.size();
vector<vector<bool>> ret(n, vector<bool>(n));
for (int i = 0; i < n; ++i)
{
ret[i][i] = true;
}
for (int L = 2; L <= n; ++L)
{
for (int i = 0; i < n; ++i)
{
int j = i + L - 1;
if (j >= n)
{
break;
}
if (L == 2)
{
ret[i][j] = (s[i] == s[j]);
}
else
{
ret[i][j] = (s[i] == s[j]) && ret[i + 1][j - 1];
}
}
}
return ret;
}
};