标签:字符串相似度 字符串逆序 字符串排列组合 字符串移位包含 字符串转整数
哈喽,各位小伙伴们。南京今天终于停雨了呢,虽然是个阴天,也是很有感觉的哦。有没有会莫文蔚《阴天》的小伙伴?
阴天,在不开灯的房间,让所有思绪一点一点沉淀。
是的,阴天就是适合一个人在房间里面沉淀的天气。昨天还和小伙伴们谈到现在大家因为谈恋爱而产生快乐依赖于对方的现象,在这儿分享给大家一句话:想要谈恋爱,咱得先在感情上能自我满足了再去。
楼主就希望借助这些算法题来沉淀和提升自己。因为楼主脑子不是很好使,天子不聪颖,就只能借助于后天的努力了啊。
说多了,楼主这篇文章可能会一直处于更新状态。因为我想把字符串中常见的面试算法提都给整理一下,今天暂时只准备了两道题。第一道是字符串移位包含的问题,第二道题是字符串相似度(距离)问题,后面再继续更新。Are you ready?
给定两个字符串s1和s2,要求判定s2是否能够被通过循环移位得到的字符串包含。例如,给定s1 = AABCD和s2 = CDAA,返回true;给定s1 = ABCD和s2 = ACBD,返回false.
暴力求解是我们的第一反应,题目要求判定s2是否能被通过循环移位得到的字符串包含,那我们就把s1和它所有的循环移位全部求出来,一个一个判断是否存在字符串包含。话不多说,见代码:
#include <iostream>
#include <string>
using namespace std;
bool isContain(char *src, char* dest);
void main()
{
char s1[] = "AABCD";
char s2[] = "CDAA";
int len = strlen(s1); //总共可移位length-1次
char temp = s1[len - 1];
if (isContain(s1, s2))
{
cout << "包含" << endl;
return;
}
for (int i = 0; i < len - 1; i++)
{
for (int j = len - 1; j > 0; j--)
{
s1[j] = s1[j - 1];
}
s1[0] = temp;
temp = s1[len - 1];
//一次右移完毕
if (isContain(s1, s2))
{
cout << "包含" << endl;
return;
}
}
cout << "不包含" << endl;
system("pause");
}
bool isContain(char *src, char* dest)
{
if (NULL != strstr(src, dest))
return true;
return false;
}
我们队循环移位之后的结果进行分析,以s1 = “ABCD”而言,我们将ABCD所有的移位之后的字符串序列写出来,如下所示:
ABCD -> DABC -> CDAB -> BCDA
可以发现,s1及其所有循环移位的序列都是ABCDABCD的子序列。那么如果s2是s1的循环移位子序列,那么s2肯定是s1s1的子序列。那么原题就转换成判断s2是不是s1s1的子序列了,不需要再进行循环移位的操作了。有同学肯定会疑问,属于s1s1的子序列,并不一定是s1循环移位的子序列啊。但是同学们可以试一试,属于s1s1的子序列,并且长度等于或者小于s1的子序列一定是s1的循环移位子序列。那么代码就可以变成:
#include <iostream>
#include <string>
using namespace std;
bool isContain(char *src, char* dest, int lenSrc, int lenDest);
void main()
{
char s1[] = "AABCD";
char s2[] = "CDAAF";
int len1 = strlen(s1); //总共可移位length-1次
int len2 = strlen(s2);
if (isContain(s1, s2, len1, len2))
cout << "包含" << endl;
else
cout << "不包含" << endl;
system("pause");
}
bool isContain(char *src, char* dest, int lenSrc, int lenDest)
{
if (lenDest > lenSrc)
return false;
char *doubleSrc = new char[2 * lenSrc + 1]();
strcpy_s(doubleSrc, 2 * lenSrc + 1, src);
strcat_s(doubleSrc, 2 * lenSrc + 1, src);
doubleSrc[2 * lenSrc + 1] = ‘\0‘;
if (NULL == strstr(doubleSrc, dest))
return false;
return true;
}
为计算将str1变换到str2所需最小操作步骤,必须先对变换操作进行定义:
1.修改一个字符(如把“a”替换为“g”);
2.增加一个字符(如把“abcd”变为“abcdz”);
3.删除一个字符(如把“travelling”变为“traveling”);
字符串变换过程中执行了上述三个操作之间的任一操作,则两字符串间距离就加1。例如将上文中的str1变换到str2,即“abcd”到“gbcdz”,通过“abcd”->(操作1)->“gbcd”->(操作2)->“gbcdz”,需进行一次修改和一次添加字符操作,故两字符串距离为2,那么字符串相似度则为距离+1的倒数,即1/3=0.333。这是由俄罗斯科学家Vladimir Levenshtein在1965年提出这个概念。因此也叫Levenshtein Distance。
很多时候看起来不知道怎么下手的题目,可以尝试一下递归法。递归法的思想就是把大问题不断转换成小问题。我们看下面一个例子,A=xabcdae和B=xfdfa,先比较第一个元素,相等,那么直接进入到下一个字符的比较。如果不相等,我们可以进行如下的操作,使得这个不相等的字符串相等起来。
操作1:删除A串的第一个字符,然后计算A[1,…, LenA - 1]和B[0, …., LenB - 1]的距离;
操作2:删除B串的第一个字符,然后计算A[0,…, LenA - 1]和B[1, …., LenB - 1]的距离;
操作3:将A的第一个字符修改为B的第一个字符,然后计算A[1,…, LenA - 1]和B[1, …., LenB - 1]的距离;
操作4:将B的第一个字符修改为A的第一个字符,然后计算A[1,…, LenA - 1]和B[1, …., LenB - 1]的距离;
操作5:增加B的第一个字符串到A的第一个字符串前面,然后计算A[0,…, LenA - 1]和B[1, …., LenB - 1]的距离;
操作6:增加A的第一个字符串到B的第一个字符串前面,然后计算A[1,…, LenA - 1]和B[0, …., LenB - 1]的距离;
我们并不在乎具体需要什么操作(删除,修改或添加),我们只关心最后的距离,所以我们将上面六种操作总结一下就是
操作1:一步操作之后,然后计算A[1,…, LenA - 1]和B[0, …., LenB - 1]的距离。比如这个操作可以是删除A串的第一个字符或者是将B的第一个字符修改为A的第一个字符,但是我们不关心具体的操作;
操作2:一步操作之后,然后计算A[0,…, LenA - 1]和B[1, …., LenB - 1]的距离;
操作3:一步操作之后,然后计算A[1,…, LenA - 1]和B[1, …., LenB - 1]的距离;
递归结束的条件:
上面的操作1-操作3只是告诉我们写程序的时候有这么三个分支,递归总是要有退出或者返回条件的啊。所以下面我们讨论一下,这个递归程序几个技术时的情况。
由上面的分析,我们可以大概知道,肯定要传入的参数是,A字符串和B字符串的地址,然后A/B的起始和末尾索引,假设分别为aStartIndex/bStartIndex和aEndIndex/bEndIndex。结束的三种情况:
情况一:A字符串全部遍历完了,那么step保留的是前面操作的步骤,bEndIndex - bStartIndex + 1保留的时候的字符串的距离,所以返回bEndIndex - bStartIndex + step + 1
情况二:B字符串全部遍历完了,那么step保留的是前面操作的步骤,bEndIndex - bStartIndex + 1保留的时候的字符串的距离,所以返回bEndIndex - bStartIndex + step + 1
下面直接看代码吧:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int min2(int a, int b);
int min3(int a, int b, int c);
int calc(char* a, char* b, int aStartIndex, int aEndIndex, int bStartIndex, int bEndIndex, int step);
int main(void)
{
char *a = "abdd";
char *b = "aebdd";
int step = 0;
printf("%d",calc(a, b, 0, strlen(a) - 1, 0, strlen(b) - 1, step));
return EXIT_SUCCESS;
}
int calc(char* a, char* b, int aStartIndex, int aEndIndex, int bStartIndex, int bEndIndex, int step)
{
int d1,d2,d3;
if (aStartIndex > aEndIndex)
{
return bEndIndex - bStartIndex + step + 1;
}
if (bStartIndex > bEndIndex)
{
return aEndIndex - aStartIndex + step + 1;
}
if (a[aStartIndex] == b[bStartIndex])
{
//如果该位置处两个字符串的字符相等,那么perfect,直接进入下一个位置的比较
return calc(a, b, aStartIndex + 1, aEndIndex, bStartIndex + 1, bEndIndex, step);
}
else
{
step ++;
d1 = calc(a, b, aStartIndex + 1, aEndIndex, bStartIndex, bEndIndex, step);
d2 = calc(a, b, aStartIndex, aEndIndex, bStartIndex + 1, bEndIndex, step);
d3 = calc(a, b, aStartIndex + 1, aEndIndex, bStartIndex + 1, bEndIndex, step);
return min3(d1, d2, d3);
}
}
int min2(int a, int b)
{
return a>b ? b : a;
}
int min3(int a, int b, int c)
{
if(a > b)
a = b;
if(a > c)
a = c;
return a;
}
很显然,递归方法最为诟病的就是重复计算的问题,用递归方法的很多都有这个问题。那我们这时候呢,又可以拿出我们的第二把刷子啦,叫做”动态递归法“,动态递归的主要原理就是保存小规模时候程序运行的结果,然后计算大规模数据的时候就可以直接调用小规模的结果了。
设str1=“abcd”,str2=“gbcdz”,定义一个二维数组d[][],d[i][j]表示str1中取前i个字符和str2中取前j个字符的最短距离,例如d[3][2]表示“abc”到“gb”的最短距离。
d[i][j]的计算规则有三条:
例如str1=“abcd”,str2=“gbcdz”的d[][]就为(注意i,j的取值范围):
代码:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int min(int a, int b, int c);
int calc(char* a, char* b);
int main(void)
{
printf("%d",calc("abcd", "gbcdz"));
return EXIT_SUCCESS;
}
int min(int a, int b, int c)
{
if(a > b)
a = b;
if(a > c)
a = c;
return a;
}
int calc(char* a, char* b)
{
int lenA = strlen(a);
int lenB = strlen(b);
int **d = new int*[lenA + 1];
for (int i = 0; i < lenA + 1; i++)
{
d[i] = new int[lenB + 1];
}
for(int i = 0;i <= lenA; ++i)
d[i][0] = i;
for(int i = 0; i <= lenB; ++i)
d[0][i] = i;
for(int i = 1; i <= lenA; ++i)
for(int j = 1; j <= lenB; ++j)
d[i][j] = min(d[i-1][j-1] + (a[i-1] == b[j-1] ? 0:1), d[i-1][j]+1, d[i][j-1]+1);
return d[lenA][lenB];
}
未完待续,敬请期待
未完待续, 敬请期待
未完待续, 敬请期待
未完待续, 敬请期待
未完待续, 敬请期待
未完待续, 敬请期待
标签:字符串相似度 字符串逆序 字符串排列组合 字符串移位包含 字符串转整数
原文地址:http://blog.csdn.net/xiaxia__/article/details/45167941