字符串是编程中最重要的一类数据结构,能否对字符串进行灵活处理是考察一个求职者最基本的要求,而且字符串在面试中占的比重也很大,接下来就针对字符串相关的算法进行简要的整理和归纳。
字符串相关问题包括最长公共子串、最长公共子序列、字符串逆序等等。
旋转字符串
问题描述:给一个字符串和一个旋转的偏移量offset,将字符串循环右移offset位。如:”abcdefg” 循环右移 4位之后变为了:”defgabc”
要求做到O(1)的额外空间耗费,O(n)的时间
思路:将字符串反转,然后分为前后两部分,分别反转,即可。代码如下
package com.xpn.string;
public class RotateString {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(new RotateString().rotateString("abcdefg",4));
}
public String rotateString(String str,int offset){
String reverseString=reverseString(str);
String str1=reverseString(reverseString.substring(0,offset));
String str2=reverseString(reverseString.substring(offset));
return str1+str2;
}
public String reverseString(String str){
StringBuffer sb=new StringBuffer();
for(int i=str.length()-1;i>=0;i--){
sb.append(str.charAt(i));
}
return sb.toString();
}
}
最长公共子串
问题描述:输入两个字符串,求最长公共子串。返回最长公共子串的长度。
解决思路:动态规划的思想进行解决,找出状态转移方程。
package com.xpn.string;
public class LCS {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(new LCS().lcs("abcdef", "cde"));
}
int lcs(String a,String b){
int max=0;//保存最大长度
int m=a.length();
int n=b.length();
int opt[][]=new int[m+1][n+1];
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(a.charAt(i-1)==b.charAt(j-1))
opt[i][j]=opt[i-1][j-1]+1;//状态转移方程
if(opt[i][j]>max){
max=opt[i][j];//保存最大
}
}
}
return max;
}
}
改进之后,打印找到的最大公共子串,代码:
package com.xpn.string;
public class LCS {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(new LCS().lcs("abcdef", "cde"));
}
int lcs(String a,String b){
int max=0;//保存最大长度
int m=a.length();
int n=b.length();
int opt[][]=new int[m+1][n+1];
int index=0;//保存找到最大长度,最后一个字符的下标
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(a.charAt(i-1)==b.charAt(j-1))
opt[i][j]=opt[i-1][j-1]+1;//状态转移方程
if(opt[i][j]>max){
max=opt[i][j];//保存最大
index=i-1;
}
}
}
StringBuilder sBuilder=new StringBuilder();
int begin=index-max;
while(index>begin){
sBuilder.append(a.charAt(index));
index--;
}
System.err.println(sBuilder.reverse().toString());//需要反转
return max;
}
}
最大公共子序列
与最大公共字符串不同的是,这里要求的序列不一定是连续的。上代码
package com.xpn.string;
public class LCS2 {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(new LCS2().lcs2("axxxbxxxcd", "abc"));
}
int lcs2(String a,String b){
int m=a.length();
int n=b.length();
int opt[][]=new int[m+1][n+1];
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(a.charAt(i-1)==b.charAt(j-1))
opt[i][j]=opt[i-1][j-1]+1;
else {
opt[i][j]=Math.max(opt[i-1][j], opt[i][j-1]);
}
}
}
return opt[m][n];
}
}
这里只是简单实现了最长公共子序列的长度,如果要进一步找出对应的字符,则需要做进一步处理。
改进之后,可以找出对应的最长公共子序列,通过标记进行处理。代码如下:
package com.xpn.string;
public class LCS2 {
/**
* @param args
*/
public static void main(String[] args) {
System.out.println(new LCS2().lcs2("axxxbxxxcd", "abcffd"));
}
int lcs2(String a,String b){
int m=a.length();
int n=b.length();
int opt[][]=new int[m+1][n+1];
char flag[][]=new char[m+1][n+1];
for(int i=1;i<=m;i++){
for(int j=1;j<=n;j++){
if(a.charAt(i-1)==b.charAt(j-1)){
opt[i][j]=opt[i-1][j-1]+1;
flag[i][j]='\';
}
else {
if(opt[i-1][j]>opt[i][j-1]){
flag[i][j]='|';
}else {
flag[i][j]='―';
}
opt[i][j]=Math.max(opt[i-1][j], opt[i][j-1]);
}
}
}
StringBuffer sBuffer=new StringBuffer();//保存结果
for(int i=1;i<=m;i++){
System.out.println();
for(int j=1;j<=n;j++){
//System.out.print(flag[i][j]+" ");
if(flag[i][j]=='\'){
sBuffer.append(b.charAt(j-1));
break;
}
}
}
System.out.println();
System.out.println(sBuffer.toString());
return opt[m][n];
}
}
这里只找出了一个,如果要找出所有的公共子序列,需要通过递归进一步实现。