题目描述
给定一个字符串 $s$,将 $s$ 划分成若干部分,使得每一部分都是回文串。
请返回所有合法的划分方案。
样例
输入:"aab"
输出:
[
["aa","b"],
["a","a","b"]
]
算法
(暴搜+剪枝) $O(2^nn)$
暴力枚举所有方案。
对字符串从前往后搜索,搜索时维护如下几个状态:
- 已经划分好的区间列表;
- 当前正在枚举的区间;
- 枚举到的字符串 $s$ 的下标;
- 字符串 $s$;
在搜索时:分情况处理:
- 如果已经遍历完字符串 $s$,则递归终止。终止前判断方案是否合法:即判断当前区间是否是回文串;
- 否则,字符串 $s$ 还没遍历完,则
- 如果当前区间不是回文串,则只能继续在该区间添加字符;
- 如果当前区间是回文串,则既可以在该区间添加字符,也可以将该区间保存,并开启一个新的区间;
时间复杂度分析:首先考虑最多有多少个合法方案,我们可以考虑在相邻字符之间放板子,每种放板子的方法都对应一种划分方案,而每个字符间隔有放和不放两种选择,所以总共有 $2^{n-1}$ 个方案。另外对于每个方案,需要 $O(n)$ 的时间记录方案。所以总时间复杂度是 $O(2^nn)$。
C++ 代码
class Solution {
public:
vector<vector<string>> ans;
vector<string> path;
vector<vector<string>> partition(string s) {
dfs("", 0, s);
return ans;
}
bool check(string &now)
{
if (now.empty()) return false;
for (int i = 0, j = now.size() - 1; i < j; i ++, j -- )
if (now[i] != now[j])
return false;
return true;
}
void dfs(string now, int u, string &s)
{
if (u == s.size())
{
if (check(now))
{
path.push_back(now);
ans.push_back(path);
path.pop_back();
}
return;
}
if (check(now))
{
path.push_back(now);
dfs("", u, s);
path.pop_back();
}
dfs(now + s[u], u + 1, s);
}
};
对于新手,我推荐去看看Coderoger的题解,我觉得更容易理解一点
请教一下大家,
时间复杂度中的 $O(2^n n)$ 中的 $O(n)$ 是从哪儿来哒?是不是调用 check 函数需要的时间?还是 push_back 记录 path 的时间?
另外,这个直接回溯的方法,相比于视频里的用动态规划预处理判断子串是否为回文串,两者哪个更好?好在哪里呢?是不是用动态规划预处理的做法,利用空间换时间,时间复杂度更低?
是不是直接回溯的时间复杂度是$O(2^n n)$,而加了动态规划的时间复杂度是 $O(2^n)$?
我感觉O(n)应该是check函数的时间复杂度,这里复制方案的时间应该是忽略掉了。
嗯嗯 好的 谢谢~
y总, 在当遍历完最后一个字符后, now是字符串, 把他压入path, 再把path加入结果再return , 为什么最后还有
path.pop_back();
好像突然明白了 ,因为第一次走到最后一个字符时候 ,只是枚举了一种方案, 需要回溯, 搜索其他的方案 是么?
对滴。
大佬,为什么dfs(now + s[u]) 可以,但如果直接dfs(“” + s[u]) 不可以呢?如何把一个字符串和一个字符进行拼接呢?
这两种写法的作用是不一样的,
now + s[u]
是将s[u]
接在now
这个字符串后面,"" + s[u]
是求""
这个临时字符串的后面某个地址。建议先看一遍C++基本语法。噢,谢谢!