题目二 相似字符串
时间限制: 4000ms 内存限制: 256MB
描述
对于两个长度相等的字符串,我们定义其距离为对应位置不同的字符数量,同时我们认为距离越近的字符串越相似。例如,“0123”和“0000”的距离为 3,“0123”和“0213”的距离则为 2,所以与“0000”相比,“0213”和“0123”最相似。
现在给定两个字符串 S1 和 S2,其中 S2 的长度不大于 S1。请在 S1 中寻找一个与 S2 长度相同的子串,使得距离最小。
输入
输入包括多组数据。第一行是整数 T,表示有多少组测试数据。每组测试数据恰好占两行,第一行为字符串 S1,第二行为 S2。所有字符串都只包括“0”到“9”的字符。
输出
对于每组测试数据,单独输出一行“Case #c: d”。其中,c 表示测试数据的编号(从 1 开始),d 表示找到的子串的最小距离。
数据范围
1 ≤ T ≤ 100
小数据:字符串长度不超过 1000
大数据:字符串长度不超过 50000
样例输入
3
0123456789
321
010203040506070809
404
20121221
211
样例输出
Case #1: 2
Case #2: 1
Case #3: 1
解题思路
这道题其实不复杂,结果我很蛋疼的用了一个字符串近似匹配的 DP 算法,结果果然悲剧了……在这里就只好读读大神们的代码,看看有什么给力的解法。
首先设两个字符串分别为 s1 和 s2,它们的长度为 $m$ 和 $n$,其中 $m \ge n$。
最简单的方法是直接暴力字符串匹配,就是尝试将每个 $s{1_{i \ldots i + n}}$ 和 $s2_{0 \ldots n}$ 进行匹配(排名第二的 Tripod2K 就是这么过的),不过需要注意如果距离已经大于当前的最小距离了,就不必继续匹配下去了,否则是会悲剧的。在比赛结束之后可能又更新测试数据后 Rejudge 了,所以 Tripod2K 的算法已经被判为超时,所以此算法仅供参考。其核心算法为:
int min = n; // 初始最小距离为 n
for(int i = 0; i <= m-n; i++) {
int dis= 0;
// 尝试匹配 s1[i...i+n] 和 s2
for(int j = 0; j < n; j++) {
if(s1[i + j] != s2[j]) dis++;
if(dis >= min) {
// 这里 dis 已经超过 min 了,再继续匹配下去也没有什么用
break;
}
}
if(dis < min) min = dis;
if(min == 0) {
// min 到达了 0,已经不可能更小了,也不必再匹配下去了
break;
}
}
完整的代码为:(链接在这里)
import java.util.Scanner;
public class Main {
public static void main(String args[]){
Scanner in = new Scanner(System.in);
int t = in.nextInt();
byte[] s1 = new byte[60000];
byte[] s2 = new byte[60000];
for(int i = 1; i <= t; i++){
s1 = in.next().getBytes();
s2 = in.next().getBytes();
int d = distance(s1, s2);
System.out.println("Case #" + i + ": " + d);
}
}
private static int distance(byte[] s1, final byte[] s2){
int l1 = s1.length;
int l2 = s2.length;
int result = l2;
for(int i = 0; i <= l1 - l2; ++i){
int tmp= 0;
for(int j = 0; j < l2; ++j){
if(s1[i + j] != s2[j]) tmp++;
if(tmp >= result) break;
}
if(tmp < result) result = tmp;
if(tmp == 0) break;
}
return result;
}
}
还有排名第四的 chaozicen 的算法,则比较巧妙。现在假设 s1="010203040506070809",s2="404",首先对 s1 进行预处理,标记出每个字符出现的索引,得到下面的表格:
字符 | 出现的索引 |
'0' | 0,2,4,6,8,10,12,14,16 |
'1' | 1 |
'2' | 3 |
'3' | 5 |
'4' | 7 |
'5' | 9 |
'6' | 11 |
'7' | 13 |
'8' | 15 |
'9' | 17 |
然后在读取 s2 的每个字符时,根据表格在一个长为 $n$ 的 int 数组 ans 上进行标记 s2[0] 出现的位置。例如现在 s2[0] = '4',表格中字符 '4' 对应的索引只有 7,那么令 ans[7-0]++;然后读取 s2[1]='0',表格中字符 '0' 对应的索引有 0,2,4,6,8,10,12,14 和 16,那么就令 ans[0-1]、ans[2-1]、ans[4-1]、ans[6-1]、ans[8-1]、ans[10-1]、ans[12-1]、ans[14-1] 和 ans[16-1] 均加一;最后读取 s2[2] = '4',令 ans[7-2]++。最后得到的 ans 数组如下所示:
ans = {0, 1, 0, 1, 0, 2, 0, 2, 0, 1, 0, 1, 0 ,1, 0, 1};
那么这个 ans 表示什么呢?ans[i] 表示 $s1_{i \ldots i+n}$ 与 $s2_{0 \ldots n}$ 中完全相同的字符的个数,最后只要统计 ans 中最大的那个,就表示距离最小。
下面是 chaozicen 的源代码,链接在这里:
#include
#include
using namespace std;
int num[10][60000],ans[60000];
int main()
{
int T,n,i,k,j,s,l1,l2,anss,t;
string s1,s2;
cin>>T;
t=1;
while (T>=t)
{
++t;
cin>>s1;cin>>s2;
l1=s1.length(); l2=s2.length();
for (i=0;i<10;++i) num[i][0]=0;
for (i=0;i=0)
ans[num[k][j]-i]++;
}
}
anss=0;
for (i=0;ianss) anss=ans[i];
anss=l2-anss;
cout<<"Case #"<
题目三是一个好复杂的搜索,我根本没仔细看,题目一我也没搞明白怎么个是最优策略。