本文介绍了有关字符串的算法第二部分的Java
代码实现,全部代码都可经过 在线编译器 直接运行,算法目录:java
N
的字符串的最长回文子串*
移到前部,而且不改变非*
的顺序C++
给定一个文本文件做为输入,查找其中最长的重复子字符串。例如,"Ask not what your country can do for you, but what you can do for your country"
中最长的重复字符串是“can do for you”
,第二长的是"your country"
。ios
这里解决问题的时候用到了 后缀数组 的思想,它指的是字符串全部右子集的集合,例如字符串abcde
,它的后缀数组就为["abcde", "bcde", "cde", "de", "e"]
。程序员
解法分为三步:算法
p
的后缀数组,把它存放在一个List
当中,这里注意去掉空格的状况。List
中的全部元素进行快速排序。快速排序的目的不在于使得整个数组有序,而在于 使得前缀差别最小的两个字符串在数组中位于相邻的位置,对于上面的例子,其排序结果为:import java.util.ArrayList;
import java.util.List;
import java.lang.String;
class Untitled {
static void quickSortStr(List<String> c, int start, int end){
if(start >= end)
return;
int pStart = start;
int pEnd = end;
int pMid = start;
String t = null;
for (int j = pStart+1; j <= pEnd; j++) {
if ((c.get(pStart)).compareTo(c.get(j)) > 0) {
pMid++;
t = c.get(pMid);
c.set(pMid, c.get(j));
c.set(j, t);
}
}
t = c.get(pStart);
c.set(pStart, c.get(pMid));
c.set(pMid, t);
quickSortStr(c, pStart, pMid-1);
quickSortStr(c, pMid+1, pEnd);
}
//得到两个字符串从第一个字符开始,相同部分的最大长度。
static int comLen(String p1, String p2){
int count = 0;
int p1Index = 0;
int p2Index = 0;
while (p1Index < p1.length()) {
if (p1.charAt(p1Index++) != p2.charAt(p2Index++))
return count;
count++;
}
return count;
}
static String longComStr(String p, int length){
List<String> dic = new ArrayList<String>();
int ml = 0 ;
for (int i = 0; i < length; i++) {
if (p.charAt(i) != ' ') {
//构造全部的后缀数组。
dic.add(p.substring(i, p.length()));
}
}
String mp = null;
//对后缀数组进行排序。
quickSortStr(dic, 0, dic.size()-1);
//打印排序后的数组用于调试。
for (int i = 0; i < dic.size(); i++) {
System.out.println("index=" + i + ",data=" + dic.get(i));
}
for (int i = 0; i < dic.size()-1; i++) {
int tl = comLen(dic.get(i), dic.get(i+1));
if (tl > ml) {
ml = tl;
mp = dic.get(i).substring(0, ml);
}
}
return mp;
}
public static void main(String[] args) {
String source = "Ask not what your country can do for you, but what you can do for your country";
System.out.println("result = " + longComStr(source, source.length()));
}
}
复制代码
>> result = can do for you
复制代码
长度为N
的字符串,求这个字符串里的最长回文子串,回文字符串 简单来讲就是一个字符串正着读和反着读是同样的。编程
这里用到的是Manacher
算法,首先须要对原始的字符串进行预处理,即在每一个字符之间加上一个标志位,这里用#
来表示,这会使得对于任意一个输入,通过处理后的字符串长度为2*len+1
,也就是说 处理后的字符串始终为奇数。数组
在上面咱们已经介绍过,回文串中最左或最右位置的字符与其对称轴的距离称为 回文半径,Manacher
定义了一个数组RL[i]
,它表示 第i
个字符为对称轴的回文串 的 最右一个字符与字符i
的闭区间所包含的字符个数,以google
为例,通过处理后的字符串为#g#o#o#g#l#e
,那么RL[i]
的值为: ui
RL[i]-1
的值就是原始字符串中,以位置
i
为对称轴的最长回文串的长度,那么接下来的问题就变成如何计算
RL[i]
数组。
首先,咱们须要两个辅助的变量maxidR
和maxid
,它表示当前计算的回文字符串中,所能触及到的最右位置,而maxid
则表示该回文串的对称轴所在位置,而RL[maxid]
为该回文串的距离。google
假设咱们此时遍历到了第i
个字符,那么这时候有两种状况:spa
在这种状况下,咱们知道p[maxid+1, .., maxid+RL[maxid]-1]
和p[maxid-1, .., maxid-RL[maxid]+1]
部分是关于p[maxid]
对称的,利用这个有效信息,能够避免一些没必要要的判断。.net
如今,咱们得到i
关于maxid
的对称点j
,这个点位于maxid
的左侧,所以,咱们已经计算过以它为中心的回文字符串长度RL[j]
,对于以p[j]
为中心的回文字符串有两种状况:
j
为中心的回文字符的最左边j-(RL[j]-1)
大于等于 maxidR
关于maxid
的对称点maxid-(maxidR-maxid)
,在这种状况下,咱们能够推断出以i
为对称点的RL[i]
的值最小为RL[j]
。i
为对称点的RL[i]
至少为(maxidR-i)+1
。固然这上面只是推测出的 最小状况,以后仍然要继续遍从来更新RL[i]
的值。
这时候没有任何的已知信息,咱们只能从i
的左右两边慢慢遍历。
class Untitled {
static int maxSynStr(char ip[], int len) {
int size = 2*len + 1;
char a[] = new char[size];
int RL[] = new int[size];
int i = 0;
int n;
while (i < len) {
a[(i<<1)+1] = ip[i];
a[(i<<1)+2] = '#';
i++;
}
a[0] = '#';
//最远字符的中心对称点。
int maxid = 0;
//探索到的最远字符。
int maxidR = 0;
int ans = 0;
RL[0] = 1;
for (i = 1; i < size; i++) {
//首先推测出i为中心的最小回文半径。
int offset = 0;
if (i < maxidR) {
//j是关于maxid在左边的对称点。
int j = maxid-(i-maxid);
//获取以前计算出的以j为中心的回文半径。
if (j-(RL[j]-1) >= maxid-(maxidR-maxid)) {
offset = RL[j]-1;
} else {
offset = maxidR-maxid;
}
}
do {
offset++;
} while(i-offset >= 0 && i+offset < size && a[i+offset] == a[i-offset]);
//最后一次是匹配失败的,所以要减去1。
offset--;
//RL[i]的值包括了本身,所以要加1。
RL[i] = offset+1;
//更新当前最大的回文半径。
if (i+offset > maxidR){
maxidR = i+offset;
maxid = i;
}
if (RL[i] > ans) {
ans = RL[i];
}
}
return ans-1;
}
public static void main(String[] args) {
char[] source = "google".toCharArray();
System.out.println("result=" + maxSynStr(source, 6));
}
}
复制代码
>> result=4
复制代码
将字符串中的*
移到前部,而且不改变非*
的顺序,例如ab**cd**e*12
,处理后为*****abcde12
。
咱们能够将整个数组分为两个部分:有可能包含*
字符的部分和必定不包含*
字符的部分。初始时候,整个数组只有有 有可能包含*
字符的部分,那么咱们就能够 从后往前 遍历,每遇到一个非*
的字符就把它放到 必定不包含*
字符的部分,因为须要保持非*
的顺序,所以须要将它插入到该部分的首部。
class Untitled {
static void moveNullCharPos(char p[], int length) {
if (length > 1) {
char t;
char c;
int lastCharIndex = length;
//必需要从后向前扫描。
for(int j = length-1; j >=0 ;j--) {
if ((c = p[j]) != '*') {
lastCharIndex--;
t = p[lastCharIndex]; p[lastCharIndex] = p[j]; p[j] = t;
}
}
}
System.out.println(p);
}
public static void main(String[] args) {
char[] source = "ab**cd**e*12".toCharArray();
moveNullCharPos(source, source.length);
}
}
复制代码
运行结果:
>> *****abcde12
复制代码
不开辟用于交换的空间,完成字符串的逆序。
这里利用的是 两次亦或等于自己 的思想。
#include <iostream>
using namespace std;
void reverWithoutTemp(char *p, int length){
int i = 0;
int j = length-1;
while (i < j) {
p[i] = p[i]^p[j];
//其实是p[i]^p[j]^p[j],这里的p[i]和p[j]指的是原始数组中的值。
p[j] = p[i]^p[j];
//其实是(p[i]^(p[i]^p[j]^p[j]))^(p[i]^p[j]^p[j]),这里的p[i]和p[j]指的是原始数组中的值。
p[i] = p[i]^p[j];
i++;j--;
}
std::cout << p << std::endl;
}
int main() {
char p[] = "1234566";
reverWithoutTemp(p, 7);
return 0;
}
复制代码
>> 6654321
复制代码
给定一段描述w
和一组关键字q
,咱们从这段描述中找出包含全部关键字的最短字符序列,这个最短字符序列就称为 最短摘要:
假设咱们的输入序列以下所示,其中w
表示非关键字的字符串,而q
则表示关键字的字符串:
w0,w1,w2,w3,q0,w4,w5,q1,w6,w7,w8,q0,w9,q1
复制代码
这里,咱们引入额外的三个变量pStart
、pEnd
和flag
数组,flag
数组用于统计pStart
和pEnd
之间关键字的命中状况。
这里说明一下flag
数组的做用,flag
数组和关键字p
数组的长度相同,每命中一个关键字,就将flag
数组的对应位置+1
,而flagSize
只有在每次遇到一个新的关键字时才更新,所以它表示flag
数组中 不重复的关键字的个数。
算法的步骤以下:
pEnd
从w[0]
开始移动,每发现一个命中的关键字,就更新flag[]
数组,直到w[pStart,..,pEnd]
包含了全部的关键字,即w0,w1,w2,w3,q0,w4,w5,q1
。pStart
,这时候pStart,..,pEnd
之间的长度将会逐渐变短,在移动的过程当中,同时更新flag[]
数组,直到pStart,...,pEnd
之间 再也不包含全部的关键字,这时候就能够求得 目前为止的最短摘要长度,即q0,w4,w5,q1
。pEnd
使得pStart,...,pEnd
从新 包含全部的关键字,再执行第二步的操做来 更新最短摘要长度,直到pEnd
遍历到w
的最后一个元素。class Untitled {
static int findKey(String[] p1, String p2) {
int len = p1.length;
for(int i = 0; i < len; i++) {
if(p1[i].equals(p2))
return i;
}
return -1;
}
//p1为原始数据,p2为全部的关键词。
static int calMinAbst(String[] p1, String[] p2) {
int p1Len = p1.length;
int p2Len = p2.length;
int r;
int shortAbs = Integer.MAX_VALUE;
int tAbs = 0;
int pBegin = 0;
int pEnd = 0;
int absBegin = 0;
int absEnd = 0;
int flagSize = 0;
int flag[] = new int[p2Len];
//初始化标志位数组。
for (int i = 0; i < p2Len; i++) {
flag[i] = 0;
}
while (pEnd < p1Len) {
//只有先找到所有的关键词才退出循环。
while (flagSize != p2Len && pEnd < p1Len) {
r = findKey(p2, p1[pEnd++]);
if (r != -1) {
if (flag[r] == 0) {
flagSize++;
}
flag[r]++;
}
}
while (flagSize == p2Len) {
if ((tAbs = pEnd-pBegin) < shortAbs) {
shortAbs = tAbs;
absBegin = pBegin;
absEnd = pEnd-1;
}
r = findKey(p2, p1[pBegin++]);
if (r != -1) {
flag[r]--;
if (flag[r] == 0) {
flagSize--;
}
}
}
}
for (int i = absBegin; i <= absEnd; i++) {
System.out.print(p1[i] + ",");
}
System.out.println("\n最短摘要长度=" + tAbs);
return shortAbs;
}
public static void main(String[] args) {
String keyword[] = {"微软", "计算机", "亚洲"};
String str[] = {
"微软","亚洲","研究院","成立","于","1998","年",",","咱们","的","使命",
"是","使","将来","的","计算机","可以","看","、","听","、","学",",",
"能","用","天然语言","与","人类","进行","交流","。","在","此","基础","上",
",","微软","亚洲","研究院","还","将","促进","计算机","在","亚太","地区",
"的","普及",",","改善","亚太","用户","的","计算","体验","。","”"
};
calMinAbst(str, keyword);
}
}
复制代码
>> 微软,亚洲,研究院,还,将,促进,计算机,
>> 最短摘要长度=7
复制代码
经典的LCS
问题,这里主要解释一下最长公共子序列的含义。最长公共子串和最长公共子序列的区别:子串是 串的一个连续的部分,子序列则是 不改变序列的顺序,而从序列中去掉任意的元素 而得到的新序列。
经典的LCS
问题,原理能够参考这篇被普遍转载的文章 程序员编程艺术第十一章:最长公共子序列问题,这里给出简要介绍一下基本的思想。
LCS
基于下面这个定理:
c[i][j]
:它表示字符串序列A
的前i
个字符组成的序列A
和字符串序列B
的前j
个字符组成的序列B
之间的最长公共子序列的长度,其中i<=A.len
,而且j<=B.len
。A[i]=B[j]
,那么A
与B
之间的最长公共子序列的最后一项必定是这个元素,也就是c[i][j] = c[i-1][j-1]+1
。A[i]!=B[j]
,则c[i][j]= max(c[i-1][j], c[i][j-1])
。c[0][j]=c[i][0]=0
。class Untitled {
static void LCS(char a[], int aLen, char b[], int bLen){
int c[][] = new int[bLen+1][aLen+1];
for (int i = 1; i < bLen+1; i++) {
for (int j = 1; j < aLen+1; j++) {
if (a[j-1] == b[i-1]) {
c[i][j] = c[i-1][j-1] + 1;
} else {
c[i][j] = (c[i-1][j]>c[i][j-1]) ? c[i-1][j]:c[i][j-1];
}
}
}
int csl = c[bLen][aLen];
char p[] = new char[csl+1];
int i = bLen, j = aLen;
while (i > 0 && j > 0 && c[i][j] > 0) {
if (c[i][j] == c[i-1][j]) {
i--;
} else if(c[i][j] == c[i][j-1]) {
j--;
} else if(c[i][j] > c[i-1][j-1]) {
p[c[i][j]] = a[j-1];
i--;j--;
}
}
for (i = 1; i <= csl; i++) {
System.out.print(p[i]);
}
}
public static void main(String[] args) {
char p1[] = "aadaae".toCharArray();
char p2[] = "adaaf".toCharArray();
LCS(p1, p1.length, p2, p2.length);
}
}
复制代码
>> adaa
复制代码