3.1. 字符串移位包含问题
方法1:
分别对字符串进行循环移1位,2位,3位…,来判断给定的字符串是否是其中一个字串.
复杂度是O(n^3)
方法2:
这也是一种利用空间换时间的方法.
代码如下, 为了简便实现,采用了C库中的字符串操作函数:
#if 0 /** 3.1*/ bool isRotate(char *s1,char* s2){int len1=strlen(s1),len2=strlen(s2);char *ss=new char[2*len1+1];strncpy(ss,s1,len1);strncat(ss,s2,len1);char *p=strstr(ss,s2);bool flag;if(p==0)flag=false;elseflag=true;delete[] ss;return true; } int main(){char *s1="ABCD";char *s2="CDAB";if(isRotate(s1,s2))printf("OK");elseprintf("NO");}#endif
也可以采用KMP算法进行加快查找: http://blog.csdn.net/linyunzju/article/details/7745553
3.2 电话号码对应英语单词问题
这个问题可以直观的用下面的图来表示
这样对于一个号码所对应的单词, 可以使用对树进行搜索的方式进行.
这种情况下采用的是DFS的方式.
下面的代码中采用了两种方式, 递归和非递归的方式. 其实这就是一个组合问题, 需要遍历所有情况. 对于非递归的情况, 其实有些类似非递归的排列组合的方式.
#if 0 char *c[10]{"","","ABC","DEF","GHI","JKL","MNO","PQRS","TUV","WXYZ" }; int total[10]={0,0,3,3,3,3,3, 4,3,4}; void transform(char *number){int sz=strlen(number);int *answer=new int[sz]();while(1){for(int i=0;i<sz;i++)printf("%c",c[number[i]-'0'][answer[i]]);printf("\n");int k=sz-1;while(k>=0){if(answer[k]<total[number[k]-'0']-1){answer[k]++;break;}else{answer[k]=0;k--;}}if(k<0)break;}delete[] answer; } void dfs(char *number,int *answer,int k,int sz){if(k==sz){for(int i=0;i<sz;i++)printf("%c",c[number[i]-'0'][answer[i]]);printf("\n");}else{for(answer[k]=0;answer[k]<total[number[k]-'0'];answer[k]++)dfs(number,answer,k+1,sz);}} void transform2(char *number){int sz=strlen(number);int *answer=new int[sz]();dfs(number,answer,0,sz);delete[] answer; } int main(){char *number="2345";transform(number);transform2(number); }#endif
对于问题二,可以采用下面的解法:
3.3 计算字符串相似度问题(编辑距离问题)
这个问题就是编辑距离的问题.
可以有两种解决方式: 递归的方式和动态规划的方式.
方法1: 递归的方式
这样就得到了下面的递归程序:
#if 0 /** 3.3*/ int calc_edit_dist(char *s1,char *s2){if(*s1=='\0'||s1==0)return strlen(s2);if(*s2=='\0'||s2==0)return strlen(s1);if(*s1==*s2){return calc_edit_dist(s1+1,s2+1);}else{int ed1=calc_edit_dist(s1,s2+1);int ed2=calc_edit_dist(s1+1,s2);int ed3=calc_edit_dist(s1+1,s2+1);if(ed1<ed2)return (ed1<ed3?ed1:ed3)+1;elsereturn (ed2<ed3?ed2:ed3)+1;} } int main(){char *s1="ab";char *s2="bb";printf("%d\n",calc_edit_dist(s1,s2));}#endif
但是递归程序计算比较慢, 因为中间存在很多的重复计算.
\
方法2: 动态规划
这个问题其实可以归结为寻找两个序列的最长公共子序列问题.
参考: http://blog.csdn.net/linyunzju/article/details/7747718
3.5 最短摘要生成问题
给定一段产品的英文描述,包含M个英文字母,每个英文单词以空格分隔,无其他标点符号;再给定N个英文单词关键字,请说明思路并编程实现方法String extractSummary(String description,String[] key words),目标是找出此产品描述中包含N个关键字(每个关键词至少出现一次)的长度最短的子串,作为产品简介输出。(不限编程语言)20分。
#if 0 bool allExisted(const map<string,int>& mp){map<string,int>::const_iterator ite;for(ite=mp.begin();ite!=mp.end();++ite)if(ite->second==0)return false;return true; } void reset(map<string,int>& mp){map<string,int>::iterator ite;for(ite=mp.begin();ite!=mp.end();++ite)ite->second=0; } int find_min_len_digest(const vector<string>& vec,const vector<string>& keywords){map<string,int> mp;int len=1000000;vector<string>::const_iterator ite,pbeg,pend;for(ite=keywords.begin();ite!=keywords.end();++ite)mp[*ite]=0;pbeg=vec.begin();while(pbeg!=vec.end()){while(pbeg!=vec.end()&&mp.find(*pbeg)==mp.end())++pbeg;if(pbeg==vec.end())break;mp[*pbeg]++;pend=pbeg+1;while(pend!=vec.end()&&!allExisted(mp)){if(mp.find(*pend)==mp.end()) {++pend;}else{mp[*pend]++;++pend;}}if(allExisted(mp)){if(pend!=vec.end()){if(len>pend-pbeg){len=pend-pbeg;}for(ite=pbeg;ite!=pend;++ite)cout<<*ite<<' ';cout<<'\n';}else{if(len>vec.size()-(pbeg-vec.begin()))len=vec.size()-(pbeg-vec.begin());for(ite=pbeg;ite!=vec.end();++ite)cout<<*ite<<' ';cout<<'\n';break;}reset(mp);}pbeg=pend;}return len; } int main(){ // string str[]={"i","am","a","big","boy",".","i","is","i","boy"}; // string key[]={"i","boy"}; string key[] = { "微软", "计算机", "亚洲"};string str[] = { "微软","亚洲","研究院","成立","于","1998","年",",","我们","的","使命","是","使","未来","的","计算机","能够","看","、","听","、","学",",","能","用","自然语言","与","人类","进行","交流","。","在","此","基础","上",",","微软","亚洲","研究院","还","将","促进","计算机","在","亚太","地区","的","普及",",","改善","亚太","用户","的","计算","体验","。","”"};vector<string> keywords(key,key+sizeof(key)/sizeof(string));vector<string> vec(str,str+sizeof(str)/sizeof(string));// copy(vec.begin(),vec.end(),ostream_iterator<string>(cout," ")); // cout<<endl; find_min_len_digest(vec,keywords);}#endif
3.6 判断两个链表是否相交
参考: http://blog.csdn.net/linyunzju/article/details/7753548
3.7 队列中取最大值操作问题
参考: http://blog.csdn.net/linyunzju/article/details/7765324
解法三:
利用两个STL中的stack适配器来实现带有最大值操作的栈类, 然后利用两个新实现的栈来实现带有最大值操作的队列.
#if 0 class Stack{public:void push(int x){sta.push(x);if(stb.empty()) {stb.push(x);}else{int t=stb.top();stb.push(t>x?t:x);}}void pop(){if(!sta.empty()){sta.pop();stb.pop();}}int top(){assert(sta.empty()==false);return sta.top();}int maxV(){assert(sta.empty()==false);return stb.top();}int size(){return sta.size();}bool empty(){return sta.empty();}private:stack<int> sta;stack<int> stb; }; class Queue{public:void push(int x){sta.push(x);}void pop(){if(stb.empty()) {while(!sta.empty()) {int t=sta.top(); sta.pop();stb.push(t);}}stb.pop();}int front(){if(stb.empty()) {while(!sta.empty()){int t=sta.top(); sta.pop();stb.push(t);}}return stb.top();}int maxV(){if(stb.empty()){return sta.maxV();}if(sta.empty()){return stb.maxV();}return max(sta.maxV(),stb.maxV());}private:Stack sta;Stack stb; }; int main(){Queue q; int a[] = {2, 3, 4, 9, 4, 5, 10, 6}; for(int i = 0; i < sizeof(a)/sizeof(int); ++i) { q.push(a[i]); } q.push(101);cout<<q.front()<<endl;cout<<"queue maxvalue = "<<q.maxV()<<endl; q.push(590);cout<<"queue maxvalue = "<<q.maxV()<<endl; int deq = q.front(); cout<<"deq = "<<deq<<endl; cout<<"queue maxvalue = "<<q.maxV()<<endl; q.pop();cout<<"queue maxvalue = "<<q.maxV()<<endl; }#endif
3.8 求二叉树中节点的最大距离
计算一个二叉树的最大距离有两个情况:
情况A: 路径经过左子树的最深节点,通过根节点,再到右子树的最深节点。
情况B: 路径不穿过根节点,而是左子树或右子树的最大距离路径,取其大者。
只需要计算这两个情况的路径距离,并取其大者,就是该二叉树的最大距离。
//思路:注意指针声明了一定要赋值,否则会报错。
// 方法一:递归法
//距离相差最远的两个结点,共有以下两种情况:
// (1)路径经过根结点,所以两个结点在根结点的不同分支上
// (2)路径不经过根结点,所以两个结点应该是次根结点中相聚最远的两个结点。(递归思想)
// 递归本质上还是采用了后续遍历的方法。由于是从叶子结点开始的所以每次处理都是第一种情况。
// 方法二:非递归法
//采用后序遍历二叉树的同时对二叉树的结点进行更新,每次更新都要更新最大距离。
struct Node{char data;Node* left;Node* right;int nMaxLeft;int nMaxRight; }; int maxLen=0; void findMaxLength(Node* root){if(root==0)return ;if(root->left==0)root->nMaxLeft=0;if(root->right==0)root->nMaxRight=0;if(root->left!=0)findMaxLength(root->left);if(root->right!=0)findMaxLength(root->right);if(root->left!=0) {root->nMaxLeft=(root->left->nMaxLeft>root->left->nMaxRight?root->left->nMaxLeft:root->left->nMaxRight)+1;}if(root->right!=0){root->nMaxRight=(root->right->nMaxLeft>root->right->nMaxRight?root->right->nMaxLeft:root->right->nMaxRight)+1;}if(root->nMaxLeft+root->nMaxRight>maxLen)maxLen=root->nMaxLeft+root->nMaxRight; } void findMaxLength2(Node* root){stack<Node*> st;Node *p=root,*visited=0;while(p!=0||!st.empty()){while(p!=0){st.push(p);p=p->left;}p=st.top();if(p->right==0||visited==p->right){if(p->left!=0) {p->nMaxLeft=(p->left->nMaxLeft>p->left->nMaxRight?p->left->nMaxLeft:p->left->nMaxRight)+1;}if(p->right!=0){p->nMaxRight=(p->right->nMaxLeft>p->right->nMaxRight?p->right->nMaxLeft:p->right->nMaxRight)+1;}if(p->nMaxLeft+p->nMaxRight>maxLen)maxLen=p->nMaxLeft+p->nMaxRight;st.pop();visited=p;p=0;}elsep=p->right;} }
这段代码有几个缺点:
算法加入了侵入式(intrusive)的资料nMaxLeft, nMaxRight
使用了全局变量 nMaxLen。每次使用要额外初始化。而且就算是不同的独立资料,也不能在多个线程使用这个函数
逻辑比较复杂,也有许多 NULL 相关的条件测试。
一种不改变树本身结构的方法:
我认为这个问题的核心是,情况A 及 B 需要不同的信息: A 需要子树的最大深度,B 需要子树的最大距离。只要函数能在一个节点同时计算及传回这两个信息,代码就可以很简单:
struct RESULT{int nMaxDistance;int nMaxDepth; }; RESULT findMaxLength3(Node* root){if(root==0){RESULT empty={0,-1};return empty;}RESULT lhs=findMaxLength3(root->left);RESULT rhs=findMaxLength3(root->right);RESULT res;res.nMaxDepth=max(lhs.nMaxDepth,rhs.nMaxDepth)+1;res.nMaxDistance=max(max(lhs.nMaxDistance,rhs.nMaxDistance),lhs.nMaxDepth+rhs.nMaxDepth+2);return res; }void postorder(Node* root){stack<Node*> st;Node* p=root,*visited=0;while(p!=0||!st.empty()){while(p!=0){st.push(p);p=p->left;}p=st.top();if(p->right==0||visited==p->right){cout<<(int)p->data<<' ';st.pop();visited=p;p=0;}else{p=p->right;}}cout<<endl; }
计算 result 的代码很清楚;nMaxDepth 就是左子树和右子树的深度加1;nMaxDistance 则取 A 和 B 情况的最大值。
为了减少 NULL 的条件测试,进入函数时,如果节点为 NULL,会传回一个 empty 变量。比较奇怪的是 empty.nMaxDepth = -1,目的是让调用方 +1 后,把当前的不存在的 (NULL) 子树当成最大深度为 0。
除了提高了可读性,这个解法的另一个优点是减少了 O(节点数目) 大小的侵入式资料,而改为使用 O(树的最大深度) 大小的栈空间。这个设计使函数完全没有副作用(side effect)。
测试代码如下:
Node* initTree() { Node* tree[10]; for(int i=0;i<10;i++) { tree[i]=(Node*)malloc(sizeof(Node)); tree[i]->nMaxLeft=0; tree[i]->nMaxRight=0; tree[i]->left=NULL; tree[i]->right=NULL; tree[i]->data=(char)i; } for(int i=0;i<=2;i++) { tree[i]->left=tree[2*i+1]; tree[i]->right=tree[2*i+2]; } tree[3]->left=tree[7]; tree[5]->right=tree[8]; return tree[0]; } int main(){findMaxLength(initTree()); printf("递归法:%d\n",maxLen); maxLen=0;findMaxLength2(initTree()); printf("非递归:%d\n",maxLen); maxLen=0;RESULT r=findMaxLength3(initTree());printf("new Method:%d\n",r.nMaxDistance); }
参考: http://blog.csdn.net/zuiaituantuan/article/details/6210317
http://www.cnblogs.com/miloyip/archive/2010/02/25/1673114.html
3.9 重建二叉树
主要是给出前序和中序或中序和后序来构造出二叉树.
这种题一般有二种形式,共同点是都已知中序序列。如果没有中序序列,是无法唯一确定一棵树的,证明略。
一、已知二叉树的前序序列和中序序列,求解树。
1、确定树的根节点。树根是当前树中所有元素在前序遍历中最先出现的元素。
2、求解树的子树。找出根节点在中序遍历中的位置,根左边的所有元素就是左子树,根右边的所有元素就是右子树。若根节点左边或右边为空,则该方向子树为空;若根节点左边和右边都为空,则根节点已经为叶子节点。
3、递归求解树。将左子树和右子树分别看成一棵二叉树,重复1、2、3步,直到所有的节点完成定位。
二、已知二叉树的后序序列和中序序列,求解树。
1、确定树的根。树根是当前树中所有元素在后序遍历中最后出现的元素。
2、求解树的子树。找出根节点在中序遍历中的位置,根左边的所有元素就是左子树,根右边的所有元素就是右子树。若根节点左边或右边为空,则该方向子树为空;若根节点左边和右边都为空,则根节点已经为叶子节点。
3、递归求解树。将左子树和右子树分别看成一棵二叉树,重复1、2、3步,直到所有的节点完成定位。
举例说明:根据已知求解二叉树
中序序列 HLDBEKAFCG
后序序列 LHDKEBFGCA
1、在后序序列LHDKEBFGCA中最后出现的元素为A,HLDBEK|A|FCG
2、在后序序列LHDKEB中最后出现的元素为B,HLD|B|EK|A|FCG
3、在后序序列LHD中最后出现的元素为D,HL|D|B|EK|A|FCG
4、在后序序列LH中最后出现的元素为H,H|L|D|B|EK|A|FCG
5、在后序序列KE中最后出现的元素为E,H|L|D|B|E|K|A|FCG
5、在后序序列FGC中最后出现的元素为C,H|L|D|B|E|K|A|F|C|G
6、所有元素都已经定位,二叉树求解完成。
A
/ \
B C
/ \ / \
D E F G
/ \
H K
\
L
下面的代码中也包括了根据两个遍历结果输出另一个结果.
#if 0 //查找根节点的位置 char* find_root(char *start,char *end,char c){while(start<=end){if(*start==c)return start;start++;}return NULL; } //根据前序和中序来输出后序:核心函数 void pre_in_post_(char *pre_start,char*pre_end,char* in_start,char *in_end){if(in_start>in_end)return;if(in_start==in_end) {printf("%c",*in_start);return ;}char c=*pre_start++;char *p=find_root(in_start,in_end,c);if(p!=NULL){int len=p-in_start;pre_in_post_(pre_start,pre_start+len-1,in_start,p-1);pre_in_post_(pre_start+len,pre_end,p+1,in_end);}printf("%c",c); } //调用上面的递归函数 void pre_in_post(char*pre,char *in){int len1=strlen(pre);int len2=strlen(in);char *pre_end=pre+len1-1;char *in_end=in+len2-1;pre_in_post_(pre,pre_end,in,in_end); } //根据后序和中序来输出前序 void post_in_pre_(char *post_start,char*post_end,char* in_start,char *in_end){if(in_start>in_end)return;if(in_start==in_end) {printf("%c",*in_start);return ;}char c=*post_end--;char *p=find_root(in_start,in_end,c);printf("%c",c);if(p!=NULL){int len=p-in_start;post_in_pre_(post_start,post_start+len-1,in_start,p-1);post_in_pre_(post_start+len,post_end,p+1,in_end);}} //调用上面的递归函数 void post_in_pre(char*post,char *in){int len1=strlen(post);int len2=strlen(in);char *post_end=post+len1-1;char *in_end=in+len2-1;post_in_pre_(post,post_end,in,in_end); } //二叉树节点定义 struct Node{char data;Node* left;Node* right;Node(){};Node(char c,Node* l=0,Node* r=0):data(c),left(l),right(r){} }; //根据前序和中序来重建二叉树 Node* rebuild_pre_in_post_(char *pre_start,char*pre_end,char* in_start,char *in_end){if(in_start>in_end)return 0;if(in_start==in_end)return new Node(*in_start);char c=*pre_start++;char *p=find_root(in_start,in_end,c);Node* r=new Node(c);if(p!=NULL){int len=p-in_start;r->left=rebuild_pre_in_post_(pre_start,pre_start+len-1,in_start,p-1);r->right=rebuild_pre_in_post_(pre_start+len,pre_end,p+1,in_end);}return r; } // Node* rebuild_pre_in_post(char*pre,char *in){int len1=strlen(pre);int len2=strlen(in);char *pre_end=pre+len1-1;char *in_end=in+len2-1;Node* root=rebuild_pre_in_post_(pre,pre_end,in,in_end);return root; } //根据后序和中序来重建二叉树 Node* rebuild_post_in_pre_(char *post_start,char*post_end,char* in_start,char *in_end){if(in_start>in_end)return 0;if(in_start==in_end)return new Node(*in_start);char c=*post_end--;char *p=find_root(in_start,in_end,c);Node* r=new Node(c);if(p!=NULL){int len=p-in_start;r->left=rebuild_post_in_pre_(post_start,post_start+len-1,in_start,p-1);r->right=rebuild_post_in_pre_(post_start+len,post_end,p+1,in_end);}return r; } // Node* rebuild_post_in_pre(char*post,char *in){int len1=strlen(post);int len2=strlen(in);char *post_end=post+len1-1;char *in_end=in+len2-1;Node* root=rebuild_post_in_pre_(post,post_end,in,in_end);return root; } //前序遍历函数 void PreOrder(Node* root){if(root!=0) {printf("%c ",root->data);PreOrder(root->left);PreOrder(root->right);} } //后序变量函数 void PostOrder(Node* root){if(root!=0) {PostOrder(root->left);PostOrder(root->right);printf("%c ",root->data);} } int main(){char *pre="ACBGEDF";char *in="GBCEADF";char *post="GBECFDA";pre_in_post(pre,in);printf("\n");post_in_pre(post,in);printf("\n");PreOrder(rebuild_pre_in_post(pre,in));printf("\n");PostOrder(rebuild_post_in_pre(post,in)); }#endif
参考: http://www.cnblogs.com/bmrs/archive/2010/08/19/SloveTree.html