字符串匹配
精确:
indexOf(String str); -- strstr(), O(mn)。
lastIndexOf(String str); -- continue 的别样用法。
matches(String regex); -- Regex.compile()/match()。
模糊:
java package?
Spell Checker -- 两个字符串的相似程度
Fuzzy Finder -- 子列匹配
上面两个问题都可以用这个概念”编辑距离”来有效解决。
所谓这个距离是指从一个字符串变到另一个字符串所需操作序列的度量;
这些操作包括:替换 shot --> spot, 插入 ago --> agog, 删除 hour --> our。
对于子列匹配,设用户正在输入的s比目标字符串t短,比如Skina是Skienna的子列,
需要两个插入操作,距离为d=2。
问题: |s| + d = |t| 是否表明s是t的子列?
设P(i, j) 表示把s[0..i] 变成t[0..j]的问题,d[i][j]为所需操作的最短距离。
P(i,j)有optimal substructure性质,即子问题P(i-1,j-1), P(i-1,j),
P(i,j-1)构成P(i,j)。证明使用cut-and-paste反证:如果其组成的子问题解
不是最优的,比如d[i-1][j-1], 把它用一个更优的解替换掉,则P(i,j)更小,
与P(i,j)最优矛盾。
带记忆的递归写法的优点是有些子问题不用解。比如背包:
P(i,j) =
P(i-1, j) if j < w[i];
min{P(i-1,j), v[i] + P(i-1,j-w[i])} if j >= w[i]
而当前问题,采用递推写法,优点是可以滚动数组以节省内存。
(i,j) 依赖于(i-1,j-1), (i-1,j), (i,j-1),因此只要按i,j递增的顺序就可以。
滚动数组d[0][] 表示d[i-1][], d[1][] 表示d[i][]。
还有就是处理边界情形(i,0)和(0,j)。
对于子问题(s[0..i], t[0..0]),
d[i][0] = 有三种情形 8(。
这里我们可以很巧妙的处理为:
把s[0]当成空字符\epsilon, 实际比较的字符都在s[1..m]。
则d[i][0] 表示把s[0..i]变成空串t[0..0],显然是把i个字符都删除。
注意上面的索引是虚拟的,实际引用字符时都要减去1。
下面我们假设插入和删除操作度量一样,记为indel()。
import java.util.Arrays;
public class EditDistance
{
char[] s, t;
int[][] d;
public EditDistance(char[] s, char[] t)
{
int i, sn = s.length, tn = t.length;
d = new int[sn][tn];
for(i = 0; i < sn; ++i)Arrays.fill(d[i], -1);
this.s = s; this.t = t;
}
int editDistance(int i, int j)
{
int rep, ins, del;
if(i < 0)return (j+1) * indel(' ');
if(j < 0)return (i+1) * indel(' ');
if(d[i][j] >= 0)return d[i][j];
rep = editDistance(i-1, j-1) + replace(s[i], t[j]);
ins = editDistance(i, j-1) + indel(t[j]);
del = editDistance(i-1, j ) + indel(s[i]);
//System.out.printf("[%d,%d,%d]", rep, ins, del);
if(rep > ins)rep = ins;
if(rep > del)rep = del;
//System.out.printf("(%d,%d,%d) ", i, j, rep);
return d[i][j] = rep;
}
int distance(){return editDistance(s.length-1, t.length-1);}
int distance3()
{
int i, j;
int rep, ins, del;
int m = s.length, n = t.length;
int[][] d = new int[2][n+1];
for(j = 0; j <= n; ++j){
d[0][j] = j * indel(' ');
}
for(i = 1; i <= m; ++i){
d[1][0] = i * indel(' ');
for(j = 1; j <= n; ++j){
rep = d[0][j-1] + replace(s[i-1], t[j-1]);
ins = d[1][j-1] + indel(t[j-1]);
del = d[0][j] + indel(s[i-1]);
if(rep > ins)rep = ins;
if(rep > del)rep = del;
d[1][j] = rep;
}
for(j = 0; j <= n; ++j)d[0][j] = d[1][j];
}
return d[1][n];
}
int replace(char a, char b)
{
if(a == b)return 0;
return 1;
}
int indel(char a)
{
return 1;
}
public static void main(String[] arg)
{
String s0 = "Skina";// "thou shalt not";
String t0 = "Skienna";// "you should not";
char[] s =s0.toCharArray();
char[] t =t0.toCharArray();
int ans, ans2;
EditDistance ed = new EditDistance(s, t);
ans = ed.distance();
ans2 = ed.distance3();
System.out.printf("ans = %d ans2 = %d%n", ans, ans2);
}
}
/*
$ javac -encoding UTF-8 EditDistance.java && java EditDistance
ans = 2 ans2 = 2
*/