关于回文字符串的一些常见问题和解法
问题1 --- 字符串的最长回文子串
思路
回文串是有中心的,选择一个位置作为回文串的中心,然后向两边扩展,便可以找到以该点作为中心的最长回文串,然后选择下一个位置作为中心,重复上述步骤,即可找到最长回文子串。由于回文串的中心不一定是某个字符,比如aa的中心是a#a中的#,因此可以进行字符串的扩展映射,将abcd扩展为#a#b#c#d,当然这并不是真的进行扩展,只是在运算时,将偶数下标当作#,奇数下标/2用来取值,扩展的目的是方便取中心位置。
代码
string longestPalindrome(string s) {
int max = 0;
int len;
int index = 0;
int size = s.size();
for(int i = 1; i < 2*size; i++){
len = i%2 + 1;
while((i+len) < 2*size && (i-len) >= 0)
if(s[(i+len)/2] == s[(i-len)/2])
len += 2;
else
break;
len --;
if(max < len){
max = len;
index = i;
}
if((max+1)/2 >= (size - i/2))
break;
}
return s.substr(index/2-max/2,max);
}
问题2 -- 回文子串个数
思路
同样使用问题1的中心扩展思想,扩展一次则个数加1
代码
int countSubstrings(string s) {
int len = s.size();
int res = 0;
int r;
for(int i = 1; i < 2*len; i++){
if(i%2) res++;
r = 1;
while(!((i+r)%2) || (i-r >= 0 && i+r < 2*len+1 && s[(i+r)/2] == s[(i-r)/2])) {
if((i+r)%2) res++;;
r++;
}
}
return res;
}
问题3 -- 分割回文串
对于一个字符串s,将其分割为回文串,返回可能的分割方案。例:s = "aba",则返回[ ["a","b","a"], ["aba"] ]; s = "aab",返回 [ ["a","a","b"], ["aa", "b"] ]。
思路
显然,本题可以通过深度优先搜索求解,搜索左边第一个可以被分割的位置,然后搜索剩余未被分割的部分即可。问题在于如何判断是否可以从某个位置分割,这要求我们验证该段是否是回文串,是才能分割该位置,因此,需要首先用动态规划将任意字串是否为回文串保存在dp[len][len]数组中,这样在DFS时直接拿出来用就好了。
此外,在进行深搜时,会出现重复搜索的情况,若想避免重复的递归调用,需要耗费内存去保存被搜索过的状态的分割方案,这样下次搜索到该状态时,不需要递归下去,直接返回分割方案即可,然而在本题中,为了避免重复搜索而使用额外内存记录状态,并不一定能优化算法,实际上,不避免重复搜索的方法效率更高,可能是记录并返回状态信息耗费了大量时间。
代码1 : 不考虑重复搜索
vector<vector<string>> res;
vector<vector<string>> partition(string s) {
int len = s.size();
if(!len) return res;
vector<vector<bool>> pt(len, vector<bool>(len, false));
pt[0][0] = true;
for(int i = 1; i < len; i++){
pt[i][i] = true;
if(s[i] == s[i-1]) pt[i-1][i] = true;
}
for(int i = 2; i < len; i++)
for(int j = 0; j < len-i; j++)
pt[j][j+i] = s[j] == s[j+i] && pt[j+1][j+i-1];
vector<string> ans;
dfs(s, ans, 0, pt);
return res;
}
void dfs(string& s, vector<string>& ans, int k, vector<vector<bool>>& pt){
if(k == s.size()) {
res.push_back(ans);
return;
}
for(int i = k; i < s.size(); i++){
if(pt[k][i]) {
ans.push_back(s.substr(k, i-k+1));
dfs(s, ans, i+1, pt);
ans.pop_back();
}
}
}
运行结果:
代码2:记录搜索状态,避免重复搜索
map<int, vector<vector<string>>> res; vector<vector<string>> partition(string s) { int len = s.size(); vector<vector<bool>> pt(len, vector<bool>(len, false)); pt[0][0] = true; for(int i = 1; i < len; i++){ pt[i][i] = true; if(s[i] == s[i-1]) pt[i-1][i] = true; } for(int i = 2; i < len; i++) for(int j = 0; j < len-i; j++) pt[j][j+i] = s[j] == s[j+i] && pt[j+1][j+i-1]; return dfs(s, 0, pt); } vector<vector<string>> dfs(string& s, int k, vector<vector<bool>>& pt){ if(res.count(k)) return res[k]; vector<vector<string>> ans; if(k==s.size()) { vector<string> t; ans.push_back(t); res[k] = ans; return ans; } for(int i = k; i < s.size(); i++){ if(pt[k][i]) { for(auto sp:dfs(s,i+1,pt)){ vector<string> split; split.push_back(s.substr(k, i-k+1)); split.insert(split.end(), sp.begin(), sp.end()); ans.push_back(split); } } } res[k] = ans; return ans; }
结果如图:
问题4 -- 分割回文串2
将一个字符串进行分割,得到的子串都是回文串,求满足要求的最少分割次数。
思路
两次动态规划结合,首先动态规划求出不同子串是否为回文串(同上题),保存在Dp[i][j]中,然后用动态规划求出前k个字符的最少分割次数,状态方程为:
dp[k] = min(dp[i]) + 1, 0<= i < k && Dp[i+1][k] == true
代码
int minCut(string s) { int len = s.size(); if(len < 2) return 0; bool pt[len][len]; int pt2[len] = {0}; pt[0][0] = true; for(int i = 1; i < len; i++){ pt[i][i] = true; pt[i-1][i] = s[i-1] == s[i]; } for(int i = 2; i < len; i++) for(int j = 0; j < len - i; j++) pt[j][j+i] = s[j] == s[j+i] && pt[j+1][j+i-1]; for(int i = 1; i < len; i++){ if(pt[0][i]) { pt2[i] = 0; continue; } int j = i; int m = pt2[j-1]; while(--j > 0){ if(!pt[j][i]) continue; m = min(pt2[j-1], m); } pt2[i] = m + 1; } return pt2[len-1]; }
原文地址:https://www.cnblogs.com/zz-zhang/p/12436830.html
- 厚土Go学习笔记 | 14. switch 的条件写的有点灵活,不过风格还是go的一贯风格
- Nodejs学习笔记(十四)— Mongoose介绍和入门
- 厚土Go学习笔记 | 13. 用循环和函数 实现Sqrt(x)
- 代码审计| 这是一款适合练手的漏洞
- 工具| NSE漏洞审计和渗透脚本的demo
- Windows Server 2008 R2 配置Exchange 2010邮件服务器并使用EWS发送邮件
- 厚土Go学习笔记 | 12. if 语句
- 厚土Go学习笔记 | 11. for循环 go语言只有for循环
- 从编译原理看一个解释器的实现
- 厚土Go学习笔记 | 10. 常量 与 数值常量
- Unity应用架构设计(7)——IoC工厂理念先行
- 厚土Go学习笔记 | 09. 类型转换 与 类型推导
- 厚积薄发,拥抱 .NET 2016
- 厚土Go学习笔记 | 08. 零值
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- DevEco Studio项目构建讲解、编写页面、布局介绍、页面跳转
- Android 天气APP(二十九)壁纸设置、图片查看、图片保存
- Chrome 私人珍藏-stylus插件实现个性化百度界面定制
- Python 基础篇-简单的异常捕获
- Python 技巧篇-让我的程序暂停一下
- Python+selenium 技术篇-浏览器后台运行
- Python 基础篇-python3安装pyHook和pywin32库
- 漫画:如何螺旋遍历二维数组?(修订版)
- 一文快速入门分库分表(必修课)
- 写出漂亮 Python 代码的 20条准则
- 简单red5+obs推流实现直播系统开发,具体设置介绍
- 使用pandas进行数据快捷加载
- 关于 JavaScript 中 null 的一切
- 总结 | DataFrame、Series、array、tensor的创建及相互转化
- 这就是你日日夜夜想要的docker!!!---------Harbor私有仓库