笔试面试题之字符串
2013-07-18 21:06
218 查看
最大子序列
最大子序列是要找出由数组成的一维数组中和最大的连续子序列。比如{5,-3,4,2}的最大子序列就是 {5,-3,4,2},它的和是8,达到最大;而 {5,-6,4,2}的最大子序列是{4,2},它的和是6。你已经看出来了,找最大子序列的方法很简单,只要前i项的和还没有小于0那么子序列就一直向后扩展,否则丢弃之前的子序列开始新的子序列,同时我们要记下各个子序列的和,最后找到和最大的子序列。
最长公共子串(LCS)
找 两个字符串的最长公共子串,这个子串要求在原字符串中是连续的。其实这又是一个序贯决策问题,可以用动态规划来求解。我们采用一个二维矩阵来记录中间的结 果。这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和"caba"(当然我们现在一眼就可以看出来最长公共子串是"ba"或"ab")
b a b
c 0 0 0
a 0 1 0
b 1 0 1
a 0 1 0
我们看矩阵的斜对角线最长的那个就能找出最长公共子串。
不过在二维矩阵上找最长的由1组成的斜对角线也是件麻烦费时的事,下面改进:当要在矩阵是填1时让它等于其左上角元素加1。
b a b
c 0 0 0
a 0 1 0
b 1 0 2
a 0 2 0
这样矩阵中的最大元素就是 最长公共子串的长度。
在构造这个二维矩阵的过程中由于得出矩阵的某一行后其上一行就没用了,所以实际上在程序中可以用一维数组来代替这个矩阵。
最长公共子序列
最长公共子序列与最长公共子串的区别在于最长公共子序列不要求在原字符串中是连续的,比如ADE和ABCDE的最长公共子序列是ADE。
我们用动态规划的方法来思考这个问题如是求解。首先要找到状态转移方程:
等号约定,C1是S1的最右侧字符,C2是S2的最右侧字符,S1‘是从S1中去除C1的部分,S2'是从S2中去除C2的部分。
LCS(S1,S2)等于下列3项的最大者:
(1)LCS(S1,S2’)
(2)LCS(S1’,S2)
(3)LCS(S1’,S2’)--如果C1不等于C2; LCS(S1',S2')+C1--如果C1等于C2;
边界终止条件:如果S1和S2都是空串,则结果也是空串。
下面我们同样要构建一个矩阵来存储动态规划过程中子问题的解。这个矩阵中的每个数字代表了该行和该列之前的LCS的长度。与上面刚刚分析出的状态转移议程相对应,矩阵中每个格子里的数字应该这么填,它等于以下3项的最大值:
(1)上面一个格子里的数字
(2)左边一个格子里的数字
(3)左上角那个格子里的数字(如果 C1不等于C2); 左上角那个格子里的数字+1( 如果C1等于C2)
精确字符串匹配BM算法
所谓精确字符串匹配问题,是在文本 T 中找到所有与查询 P 精确匹配的子串。而 BM 算法可以非常有效地解决这个问题,让时间复杂度降到低于线形的水平。
BM 算法主要用了三种巧妙而有效的方法,即从右到左扫描,坏字符规则和好后缀规则。
从右到左扫描的意思是从最后一个字符开始向前匹配,而不是习惯上的从开头向后匹配。
坏字符规则是,从右到左的扫描过程中,发现 Ti 与 Pj 不同,如果P 中存在一个字符 Pk 与 Ti 相同,且 k<i 那么就将直接将 P 向右移使 Pk 与 Ti 对齐,然后再从右到左进行匹配。如果 P 中不存在任何与 Ti 相同的字符,则直接将P 的第一个字符与 Ti 的下一个字符对齐,再从右到左进行比较。
如图:
T: a b c b a d f t a t e
P: c b a x a d
P: c b a x a d
用 R(x) 表示字符 x 在 P 中出现的最右位置,此例中 R(b)=2。
可以看出使用从右到左扫描和坏字符规则可以跳过 T 中的很多位置不去检查,从而使时间复杂度低于线性。
好后缀规则是,从右到左的扫描过程中,发现 Ti 与 Pj 不同,检查一下相同的部分 t 是否在 P 中的其他位置 t'出现,a) 如果 t 与 t' 的前一个字母不相同,就将 P 向右移,使 t' 与 T 中的 t 对齐。b) 如果 t' 没有出现,则找到与 t 的后缀相同的 P 的最长前缀 x,向右移动P ,使 x 与 T 中 t 的后缀相对应。
如图a):
N: 1
N: 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8
T: a b c b a d f t b c f a q v t b c e...
P: c b c a b c e a b c
P: c b c a b c e a b c f
可见,并不是将 P 向右移让 P5 与 T9 对齐,而是让 P2 与 T9 对齐,因为 P1 与 P8 不相同。用 L(i) 表示 t' 的最大位置,此例中, L(9)= 3。
如图b):
N: 1
N:
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8
T: a b c b a d f t b c f a q v t b c e...
P: b c c a b c e t b c
P: b c c a b c e t b c
可见,当 P 向左找不到 “tbc”时,就找到 “tbc”的最长与 P 的前缀匹配的后缀,并将 P 向右移。用 l(i) 表示这个最长后缀的长度,这个例子中 i=8。
字符串拷贝函数
字符串的倒序输出
常规方法
最大子序列是要找出由数组成的一维数组中和最大的连续子序列。比如{5,-3,4,2}的最大子序列就是 {5,-3,4,2},它的和是8,达到最大;而 {5,-6,4,2}的最大子序列是{4,2},它的和是6。你已经看出来了,找最大子序列的方法很简单,只要前i项的和还没有小于0那么子序列就一直向后扩展,否则丢弃之前的子序列开始新的子序列,同时我们要记下各个子序列的和,最后找到和最大的子序列。
#include<iostream> using namespace std; int MaxSubSeq(const int *arr,int len,int *start,int *end){ int max=0; //记录目前找到的最大子序列的和 int sum=0; //记录当前子序列的和 int begin=0,finish=0; //记录当前子序列的起始下标 *start=begin;*end=finish; //记录最长子序列的起始下标 for(int i=0;i<len;i++){ sum+=arr[i]; finish=i; if(sum>max){ max=sum; *end=finish; *start=begin; } if(sum<=0){ sum=0; begin=i+1; } } return max; } int main(){ int arr[6]={5,-3,-2,12,9,-1}; int start,end; int max=MaxSubSeq(arr,6,&start,&end); cout<<"The MaxSubSeq is from position "<<start<<"to position "<<end<<"."<<endl; cout<<"Sum of MaSubSeq: "<<max<<endl; return 0; }
最长公共子串(LCS)
找 两个字符串的最长公共子串,这个子串要求在原字符串中是连续的。其实这又是一个序贯决策问题,可以用动态规划来求解。我们采用一个二维矩阵来记录中间的结 果。这个二维矩阵怎么构造呢?直接举个例子吧:"bab"和"caba"(当然我们现在一眼就可以看出来最长公共子串是"ba"或"ab")
b a b
c 0 0 0
a 0 1 0
b 1 0 1
a 0 1 0
我们看矩阵的斜对角线最长的那个就能找出最长公共子串。
不过在二维矩阵上找最长的由1组成的斜对角线也是件麻烦费时的事,下面改进:当要在矩阵是填1时让它等于其左上角元素加1。
b a b
c 0 0 0
a 0 1 0
b 1 0 2
a 0 2 0
这样矩阵中的最大元素就是 最长公共子串的长度。
在构造这个二维矩阵的过程中由于得出矩阵的某一行后其上一行就没用了,所以实际上在程序中可以用一维数组来代替这个矩阵。
#include<iostream> #include<cstring> #include<vector> using namespace std; //str1为横向,str2这纵向 const string LCS(const string& str1,const string& str2){ int xlen=str1.size(); //横向长度 vector<int> tmp(xlen); //保存矩阵的上一行 vector<int> arr(tmp); //当前行 int ylen=str2.size(); //纵向长度 int maxele=0; //矩阵元素中的最大值 int pos=0; //矩阵元素最大值出现在第几列 for(int i=0;i<ylen;i++){ string s=str2.substr(i,1); arr.assign(xlen,0); //数组清0 for(int j=0;j<xlen;j++){ if(str1.compare(j,1,s)==0){ if(j==0) arr[j]=1; else arr[j]=tmp[j-1]+1; if(arr[j]>maxele){ maxele=arr[j]; pos=j; } } } // { // vector<int>::iterator iter=arr.begin(); // while(iter!=arr.end()) // cout<<*iter++; // cout<<endl; // } tmp.assign(arr.begin(),arr.end()); } string res=str1.substr(pos-maxele+1,maxele); return res; } int main(){ string str1("21232523311324"); string str2("312123223445"); string lcs=LCS(str1,str2); cout<<lcs<<endl; return 0; }
最长公共子序列
最长公共子序列与最长公共子串的区别在于最长公共子序列不要求在原字符串中是连续的,比如ADE和ABCDE的最长公共子序列是ADE。
我们用动态规划的方法来思考这个问题如是求解。首先要找到状态转移方程:
等号约定,C1是S1的最右侧字符,C2是S2的最右侧字符,S1‘是从S1中去除C1的部分,S2'是从S2中去除C2的部分。
LCS(S1,S2)等于下列3项的最大者:
(1)LCS(S1,S2’)
(2)LCS(S1’,S2)
(3)LCS(S1’,S2’)--如果C1不等于C2; LCS(S1',S2')+C1--如果C1等于C2;
边界终止条件:如果S1和S2都是空串,则结果也是空串。
下面我们同样要构建一个矩阵来存储动态规划过程中子问题的解。这个矩阵中的每个数字代表了该行和该列之前的LCS的长度。与上面刚刚分析出的状态转移议程相对应,矩阵中每个格子里的数字应该这么填,它等于以下3项的最大值:
(1)上面一个格子里的数字
(2)左边一个格子里的数字
(3)左上角那个格子里的数字(如果 C1不等于C2); 左上角那个格子里的数字+1( 如果C1等于C2)
#include<iostream> #include<cstring> #include<stack> #include<utility> #define LEFTUP 0 #define LEFT 1 #define UP 2 using namespace std; int Max(int a,int b,int c,int *max){ //找最大者时a的优先级别最高,c的最低.最大值保存在*max中 int res=0; //res记录来自于哪个单元格 *max=a; if(b>*max){ *max=b; res=1; } if(c>*max){ *max=c; res=2; } return res; } //调用此函数时请注意把较长的字符串赋给str1,这主要是为了在回溯最长子序列时节省时间。如果没有把较长的字符串赋给str1不影响程序的正确执行。 string LCS(const string &str1,const string &str2){ int xlen=str1.size(); //横向长度 int ylen=str2.size(); //纵向长度 if(xlen==0||ylen==0) //str1和str2中只要有一个为空,则返回空 return ""; pair<int,int> arr[ylen+1][xlen+1]; //构造pair二维数组,first记录数据,second记录来源 for(int i=0;i<=xlen;i++) //首行清0 arr[0][i].first=0; for(int j=0;j<=ylen;j++) //首列清0 arr[j][0].first=0; for(int i=1;i<=ylen;i++){ char s=str2.at(i-1); for(int j=1;j<=xlen;j++){ int leftup=arr[i-1][j-1].first; int left=arr[i][j-1].first; int up=arr[i-1][j].first; if(str1.at(j-1)==s) //C1==C2 leftup++; int max; arr[i][j].second=Max(leftup,left,up,&arr[i][j].first); // cout<<arr[i][j].first<<arr[i][j].second<<" "; } // cout<<endl; } /*矩阵构造完毕*/ //回溯找出最长公共子序列 stack<int> st; int i=ylen,j=xlen; while(!(i==0&&j==0)){ if(arr[i][j].second==LEFTUP){ if(arr[i][j].first==arr[i-1][j-1].first+1) st.push(i); --i; --j; } else if(arr[i][j].second==LEFT){ --j; } else if(arr[i][j].second==UP){ --i; } } string res=""; while(!st.empty()){ int index=st.top()-1; res.append(str2.substr(index,1)); st.pop(); } return res; } int main(){ string str1="GCCCTAGCG"; string str2="GCGCAATG"; string lcs=LCS(str1,str2); cout<<lcs<<endl; return 0; }
精确字符串匹配BM算法
所谓精确字符串匹配问题,是在文本 T 中找到所有与查询 P 精确匹配的子串。而 BM 算法可以非常有效地解决这个问题,让时间复杂度降到低于线形的水平。
BM 算法主要用了三种巧妙而有效的方法,即从右到左扫描,坏字符规则和好后缀规则。
从右到左扫描的意思是从最后一个字符开始向前匹配,而不是习惯上的从开头向后匹配。
坏字符规则是,从右到左的扫描过程中,发现 Ti 与 Pj 不同,如果P 中存在一个字符 Pk 与 Ti 相同,且 k<i 那么就将直接将 P 向右移使 Pk 与 Ti 对齐,然后再从右到左进行匹配。如果 P 中不存在任何与 Ti 相同的字符,则直接将P 的第一个字符与 Ti 的下一个字符对齐,再从右到左进行比较。
如图:
T: a b c b a d f t a t e
P: c b a x a d
P: c b a x a d
用 R(x) 表示字符 x 在 P 中出现的最右位置,此例中 R(b)=2。
可以看出使用从右到左扫描和坏字符规则可以跳过 T 中的很多位置不去检查,从而使时间复杂度低于线性。
好后缀规则是,从右到左的扫描过程中,发现 Ti 与 Pj 不同,检查一下相同的部分 t 是否在 P 中的其他位置 t'出现,a) 如果 t 与 t' 的前一个字母不相同,就将 P 向右移,使 t' 与 T 中的 t 对齐。b) 如果 t' 没有出现,则找到与 t 的后缀相同的 P 的最长前缀 x,向右移动P ,使 x 与 T 中 t 的后缀相对应。
如图a):
N: 1
N: 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8
T: a b c b a d f t b c f a q v t b c e...
P: c b c a b c e a b c
P: c b c a b c e a b c f
可见,并不是将 P 向右移让 P5 与 T9 对齐,而是让 P2 与 T9 对齐,因为 P1 与 P8 不相同。用 L(i) 表示 t' 的最大位置,此例中, L(9)= 3。
如图b):
N: 1
N:
1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8
T: a b c b a d f t b c f a q v t b c e...
P: b c c a b c e t b c
P: b c c a b c e t b c
可见,当 P 向左找不到 “tbc”时,就找到 “tbc”的最长与 P 的前缀匹配的后缀,并将 P 向右移。用 l(i) 表示这个最长后缀的长度,这个例子中 i=8。
[预处理] 输入查询字符串 P, 计算 P 中每个位置的 L(i) 和 l(i),并计算 R(i)。 [查询] k:=n; // n 是 T 中字符的总数 while k<=m do begin i :=n; // i 表示 P 中字符的位置 h :=k; // h 表示 T 中字符的位置 while i>0 and P(i)=T(i) do begin i:=i-1; h:=h-1; end; if i=0 then begin 输出 T 的这个位置上的字符串; k:= k+n-l(2); end else 移动 P(增加 k),k 取 好后缀规则和坏字符规则决定的最大值 end;
字符串拷贝函数
#include "stdafx.h" using namespace std; /* * 说明:字符串拷贝 * 参数:dest目标地址,src源地址 * 返回:返回拷贝好的地址;如果出错或者有重叠,无定义 * 异常:可能出现字符串溢出,及dest所占空间不如src所占空间大。 */ char *strcpy(char *dest , const char *src) { //调试时,使用断言,入口检测 assert( (dest!=NULL) && (src!=NULL) ); //注意这里的内存指向参数dest所在的内存,不是栈内存,因而可以在函数中返回 char *to = dest; //主要操作在while条件中完成 while( (*dest++ = *src++)!='\0'); //返回拷贝字符串首地址,方便连缀,比如strlen(strcpy(dest,"hello")) return -1; }
字符串的倒序输出
常规方法
#include <stdio.h> #include <string.h> void StringReverse(char *str) { if(NULL == str) { return ; } char *pBegin = str; char *pEnd = str + strlen(str) - 1; while(pBegin < pEnd) { char tmp = *pBegin; *pBegin = *pEnd; *pEnd = tmp; pBegin ++, pEnd --; } } int main() { char str[] = "123456789"; StringReverse(str); printf("%s\n",str); }链表形式
void PrintListReversely(ListNode* pListHead) { if(pListHead != NULL) { // Print the next node first if (pListHead->m_pNext != NULL) { PrintListReversely(pListHead->m_pNext); } // Print this node printf("%d", pListHead->m_nKey); } }
相关文章推荐
- 在字符串中找到子字符串第一次出现的位置---笔试面试题
- 【C语言】【笔试题】【面试题】实现一个函数,可以左旋字符串中的k个字符
- 字符字符串字符串笔试面试题
- 笔试面试题(7)--字符串的复制
- Java中字符串的创建与储存(附:常见笔试面试题)
- 笔试面试题11--整数与字符串转化
- 笔试面试题解备忘1:字符串转换成整数
- 【C语言【面试题】【笔试题】题目:在字符串中找出第一个只出现一次的字符。
- 笔试面试题(9)----字符串全排列
- 【C语言】【笔试题】【面试题】判断一个字符串是否为另外一个字符串旋转之后的字符串
- 笔试面试题12--字符串拷贝、链接、比较无库函数实现
- C++笔试题(剑指offer 面试题4 替换字符串中的空格)
- 【C语言】【笔试题】【面试题】判断一个字符串是否为另外一个字符串旋转之后的字符串
- 【C语言】【面试题】【笔试题】.字符串替换空格:请实现一个函数,把字符串中的每个空格替换成“%20”。
- 【C语言】【笔试题】【面试题】实现一个函数,可以左旋字符串中的k个字符
- 【C语言】【笔试题】【面试题】实现一个函数,可以左旋字符串中的k个字符
- 嵌入式笔试面试题(12)---字符串和字符的笔试题
- 【IT笔试面试题整理】字符串的组合
- 【C语言】【面试题】【笔试题】.字符串替换空格:请实现一个函数,把字符串中的每个空格替换成“%20”。
- 【IT笔试面试题整理】字符串转数组+数组转字符串