字符串的最长连接
看到的一道面试题:有 n 个长为 m+1 的字符串,如果某个字符串的最后 m 个字符与某个字符串的前 m 个字符匹配,则两个字符串可以联接, 问这 n 个字符串最多可以连成一个多长的字符串,如果出现循环,则返回错误。
根据网友的思路,可以用拓扑排序来求解,抽空实现了一次,大致如下。
首先转化为图论问题,每个字符串抽象为1个节点,如果2个字符串能联接则表示一条有向边。问题转换为找到最长的一条路径,并且检查是否存在环(回路)。
寻找路径的方式类似拓扑排序,从入度为0的节点开始,访问它的所有联通节点,访问节点的时候顺带记录当前的最长路径,当节点的访问次数的等于入度的时候(相当于入度被减为0)则可以从这个节点开始后续的遍历。
在所有入度为0的节点遍历结束后,如果存在节点的访问次数小于入度(即入度没有被减为0),则代表出现了环。
另外,在构建图的连接表的时候需要比较字符串,因为无聊,下面将字符串映射为唯一的md5键值,用键值来代替字符串的比较。
#include <windows.h>
typedef struct {
unsigned long i[2];
unsigned long buf[4];
unsigned char in[64];
unsigned char digest[16];
} MD5_CTX;
//声明函数指针的时候必须加上winapi指示,否则运行会出错
typedef void (WINAPI* PMd5Init) (MD5_CTX*);
typedef void (WINAPI* PMd5Update) (MD5_CTX*, unsigned char*, unsigned int);
typedef void (WINAPI* PMd5Final) (MD5_CTX*);
void calcMd5(char* s, int len,MD5_CTX* md5)
{
HINSTANCE hDll = LoadLibrary(L"Cryptdll.dll");
if(hDll != NULL)
{
PMd5Init md5Init = (PMd5Init)GetProcAddress(hDll,"MD5Init");
PMd5Update md5Update = (PMd5Update)GetProcAddress(hDll,"MD5Update");
PMd5Final md5Final = (PMd5Final)GetProcAddress(hDll,"MD5Final");
md5Init(md5);
md5Update(md5,(unsigned char*)s,len);
md5Final(md5);
}
}
//保存每个节点的访问信息
struct VISIT_INFO{
int visitTime; //访问次数
int inDegree; //节点的入度
vector<int> path; //最大的路径
};
//从节点head开始遍历图G,visitInfo中记录了每个节点的访问次数、入度、当前最长路径
void doTopoSort(int head,const vector<vector<int> >& G, vector<VISIT_INFO>& visitInfo)
{
visitInfo[head].path.push_back(head);
int nodeCnt = G.size();
for(int i=0; i < nodeCnt; ++i)
{
if(G[head][i] == 1 )
{
if( visitInfo[i].path.size() < visitInfo[head].path.size())
{
visitInfo[i].path = visitInfo[head].path;//保存最长路径
}
visitInfo[i].visitTime++;
//和head连通的节点i的入度被减为0,可以从i开始新一轮的递归
if(visitInfo[i].visitTime == visitInfo[i].inDegree)
{
doTopoSort(i,G,visitInfo);
}
}
}
}
//非递归求解,遍历图G,visitInfo中记录了每个节点的访问次数、入度、当前最长路径
void doTopoSort(const vector<vector<int> >& G, vector<VISIT_INFO>& visitInfo)
{
std::deque<int> queue; //存放入度为0的节点
int nodeCnt = G.size();
for(int i=0; i < nodeCnt; ++i)
{
if(visitInfo[i].inDegree == 0)
{
queue.push_back(i);
visitInfo[i].path.push_back(i);
}
}
while(queue.size() > 0)
{
int currNode = queue.front();
queue.pop_front();
for(int i=0; i < nodeCnt; ++i)
{
if(G[currNode][i] == 1 )
{
if( visitInfo[i].path.size() < visitInfo[currNode].path.size())
{
visitInfo[i].path = visitInfo[currNode].path;
}
visitInfo[i].visitTime++;
if(visitInfo[i].visitTime == visitInfo[i].inDegree)
{
queue.push_back(i);
visitInfo[i].path.push_back(i);
}
}
}
}
}
//转存md5值
struct CODE{
unsigned char digest[16];
};
//返回最长字符串的长度,第1个入参为字符串数组,第二个入参为字符串的个数
int longestString(char** s, int n)
{
if(s == NULL){return -1;}
//保存每个字符串的串首和串尾(len-1长度)对应的md5编码
vector<vector<CODE> > stringCode(n,vector<CODE>(2));
MD5_CTX md5;
int len = strlen(s[0]);
for(int i=0; i < n; ++i)
{
assert( len == strlen(s[i]));
calcMd5(s[i],len-1,&md5);//计算串首的md5值
memcpy(&stringCode[i][0],md5.digest,sizeof(CODE)) ;
calcMd5(s[i]+1,len-1,&md5);
memcpy(&stringCode[i][1],md5.digest,sizeof(CODE)) ;
}
//nxn的矩阵G用来保存字符串之间的链接关系,如果a->b可以联接,则G[a][b]==1
vector<vector<int> > G(n, vector<int>(n,0));VISIT_INFO temp={0,0};
vector<VISIT_INFO> visitInfo(n,temp);
for(int i=0; i < n; ++i){
for(int j=0; j < n; ++j)
{
if(memcmp(&stringCode[i][1],&stringCode[j][0],sizeof(CODE))==0
&& j!=i) //不允许出现自己和自己连接
{
G[i][j] = 1;
visitInfo[j].inDegree++;
}
}
}
//topo排序的递归版本
for(int i=0; i < n; ++i)
{
if(visitInfo[i].inDegree == 0)
{
doTopoSort(i,G,visitInfo);
}
}
//topo排序的非递归版本
//doTopoSort(G,visitInfo);
int maxLen = -1,maxNode;
for(int i = 0; i < n; ++i)
{
if(visitInfo[i].visitTime < visitInfo[i].inDegree)
{
return -1; //访问次数小于入度,出现了环
}
if(int(visitInfo[i].path.size()) > maxLen)
{
maxNode = i;
maxLen = visitInfo[i].path.size();
}
}
for(size_t i=0; i < visitInfo[maxNode].path.size(); ++i)
{
int idx = visitInfo[maxNode].path[i];
printf("%s->",s[idx]);
}
printf("\n");
return maxLen+len-1;
}
根据网友的思路,可以用拓扑排序来求解,抽空实现了一次,大致如下。
首先转化为图论问题,每个字符串抽象为1个节点,如果2个字符串能联接则表示一条有向边。问题转换为找到最长的一条路径,并且检查是否存在环(回路)。
寻找路径的方式类似拓扑排序,从入度为0的节点开始,访问它的所有联通节点,访问节点的时候顺带记录当前的最长路径,当节点的访问次数的等于入度的时候(相当于入度被减为0)则可以从这个节点开始后续的遍历。
在所有入度为0的节点遍历结束后,如果存在节点的访问次数小于入度(即入度没有被减为0),则代表出现了环。
另外,在构建图的连接表的时候需要比较字符串,因为无聊,下面将字符串映射为唯一的md5键值,用键值来代替字符串的比较。
#include <windows.h>
typedef struct {
unsigned long i[2];
unsigned long buf[4];
unsigned char in[64];
unsigned char digest[16];
} MD5_CTX;
//声明函数指针的时候必须加上winapi指示,否则运行会出错
typedef void (WINAPI* PMd5Init) (MD5_CTX*);
typedef void (WINAPI* PMd5Update) (MD5_CTX*, unsigned char*, unsigned int);
typedef void (WINAPI* PMd5Final) (MD5_CTX*);
void calcMd5(char* s, int len,MD5_CTX* md5)
{
HINSTANCE hDll = LoadLibrary(L"Cryptdll.dll");
if(hDll != NULL)
{
PMd5Init md5Init = (PMd5Init)GetProcAddress(hDll,"MD5Init");
PMd5Update md5Update = (PMd5Update)GetProcAddress(hDll,"MD5Update");
PMd5Final md5Final = (PMd5Final)GetProcAddress(hDll,"MD5Final");
md5Init(md5);
md5Update(md5,(unsigned char*)s,len);
md5Final(md5);
}
}
//保存每个节点的访问信息
struct VISIT_INFO{
int visitTime; //访问次数
int inDegree; //节点的入度
vector<int> path; //最大的路径
};
//从节点head开始遍历图G,visitInfo中记录了每个节点的访问次数、入度、当前最长路径
void doTopoSort(int head,const vector<vector<int> >& G, vector<VISIT_INFO>& visitInfo)
{
visitInfo[head].path.push_back(head);
int nodeCnt = G.size();
for(int i=0; i < nodeCnt; ++i)
{
if(G[head][i] == 1 )
{
if( visitInfo[i].path.size() < visitInfo[head].path.size())
{
visitInfo[i].path = visitInfo[head].path;//保存最长路径
}
visitInfo[i].visitTime++;
//和head连通的节点i的入度被减为0,可以从i开始新一轮的递归
if(visitInfo[i].visitTime == visitInfo[i].inDegree)
{
doTopoSort(i,G,visitInfo);
}
}
}
}
//非递归求解,遍历图G,visitInfo中记录了每个节点的访问次数、入度、当前最长路径
void doTopoSort(const vector<vector<int> >& G, vector<VISIT_INFO>& visitInfo)
{
std::deque<int> queue; //存放入度为0的节点
int nodeCnt = G.size();
for(int i=0; i < nodeCnt; ++i)
{
if(visitInfo[i].inDegree == 0)
{
queue.push_back(i);
visitInfo[i].path.push_back(i);
}
}
while(queue.size() > 0)
{
int currNode = queue.front();
queue.pop_front();
for(int i=0; i < nodeCnt; ++i)
{
if(G[currNode][i] == 1 )
{
if( visitInfo[i].path.size() < visitInfo[currNode].path.size())
{
visitInfo[i].path = visitInfo[currNode].path;
}
visitInfo[i].visitTime++;
if(visitInfo[i].visitTime == visitInfo[i].inDegree)
{
queue.push_back(i);
visitInfo[i].path.push_back(i);
}
}
}
}
}
//转存md5值
struct CODE{
unsigned char digest[16];
};
//返回最长字符串的长度,第1个入参为字符串数组,第二个入参为字符串的个数
int longestString(char** s, int n)
{
if(s == NULL){return -1;}
//保存每个字符串的串首和串尾(len-1长度)对应的md5编码
vector<vector<CODE> > stringCode(n,vector<CODE>(2));
MD5_CTX md5;
int len = strlen(s[0]);
for(int i=0; i < n; ++i)
{
assert( len == strlen(s[i]));
calcMd5(s[i],len-1,&md5);//计算串首的md5值
memcpy(&stringCode[i][0],md5.digest,sizeof(CODE)) ;
calcMd5(s[i]+1,len-1,&md5);
memcpy(&stringCode[i][1],md5.digest,sizeof(CODE)) ;
}
//nxn的矩阵G用来保存字符串之间的链接关系,如果a->b可以联接,则G[a][b]==1
vector<vector<int> > G(n, vector<int>(n,0));VISIT_INFO temp={0,0};
vector<VISIT_INFO> visitInfo(n,temp);
for(int i=0; i < n; ++i){
for(int j=0; j < n; ++j)
{
if(memcmp(&stringCode[i][1],&stringCode[j][0],sizeof(CODE))==0
&& j!=i) //不允许出现自己和自己连接
{
G[i][j] = 1;
visitInfo[j].inDegree++;
}
}
}
//topo排序的递归版本
for(int i=0; i < n; ++i)
{
if(visitInfo[i].inDegree == 0)
{
doTopoSort(i,G,visitInfo);
}
}
//topo排序的非递归版本
//doTopoSort(G,visitInfo);
int maxLen = -1,maxNode;
for(int i = 0; i < n; ++i)
{
if(visitInfo[i].visitTime < visitInfo[i].inDegree)
{
return -1; //访问次数小于入度,出现了环
}
if(int(visitInfo[i].path.size()) > maxLen)
{
maxNode = i;
maxLen = visitInfo[i].path.size();
}
}
for(size_t i=0; i < visitInfo[maxNode].path.size(); ++i)
{
int idx = visitInfo[maxNode].path[i];
printf("%s->",s[idx]);
}
printf("\n");
return maxLen+len-1;
}
// 123--> 231--->314
// / \ \
//023--> 311 \
// \ \
// 114-->142-->426-->267
// \
// 425
static void test3()
{
char* s[10]={"123",
"231",
"023",
"314",
"142",
"426",
"267",
"311",
"114",
"425"};
assert(9==longestString(s,10));
}
// 123--> 231--->314
// / \ \
//023--> 311 \
// \ \ \
// \ 114-->142-->426-->267
// \ \
// <------------- 423
static void test4()
{
char* s[10]={"123",
"231",
"023",
"314",
"142",
"426",
"267",
"311",
"114",
"423"};
assert(-1==longestString(s,10));
}
// 123--> 231--->314
// / \ \
//002-->023--> 311 \
// \ \
// 114-->142-->426-->267
// \
// 425
static void test5()
{
char* s[11]={"123",
"231",
"023",
"314",
"142",
"426",
"267",
"311",
"114",
"425",
"002"};
assert(10==longestString(s,11));
}
void testLongestString()
{
test3();
test4();
test5();
}
// / \ \
//023--> 311 \
// \ \
// 114-->142-->426-->267
// \
// 425
static void test3()
{
char* s[10]={"123",
"231",
"023",
"314",
"142",
"426",
"267",
"311",
"114",
"425"};
assert(9==longestString(s,10));
}
// 123--> 231--->314
// / \ \
//023--> 311 \
// \ \ \
// \ 114-->142-->426-->267
// \ \
// <------------- 423
static void test4()
{
char* s[10]={"123",
"231",
"023",
"314",
"142",
"426",
"267",
"311",
"114",
"423"};
assert(-1==longestString(s,10));
}
// 123--> 231--->314
// / \ \
//002-->023--> 311 \
// \ \
// 114-->142-->426-->267
// \
// 425
static void test5()
{
char* s[11]={"123",
"231",
"023",
"314",
"142",
"426",
"267",
"311",
"114",
"425",
"002"};
assert(10==longestString(s,11));
}
void testLongestString()
{
test3();
test4();
test5();
}