面试题目积累
一、判断字符串a中是否包含字符串b(或者字符串a中包含字符串b的字母个数)
1。使用最笨的方法,依次扫描
//时间复杂度为O(lenLong*lenShort) int isContain(char* longString,char* shortString){ int lenLong=strlen(longString); int lenShort=strlen(shortString); if(lenLong<lenShort){ return 0; } int j; for(int i=0;i<lenShort;i++){ for(j=0;j<lenLong;j++){ if(longString[j]==shortString[i]){ break; } } if(j==lenLong){ cout<<"false"<<endl; return 0; } } cout<<"true"<<endl; return 1; }
2,排序之后再扫描(其中采用快速排序的复杂度为O(mlogm+nlogn)+o(m+n)),采用计数排序的复杂度为O(m+n)\
//方法二,先对两个字符串中的字母进行排序,然后对两个字符串进行轮循 //两个字符串排序,复杂度为(mlogm+nlogn),之后扫描需要o(m+n),因此一共的复杂度为o(mlogm)+o(nlogn)+o(m+n) ///首先,实现对于字符的快速排序算法 int partition(char* str,int low,int high){ int i=low; int j=high; int pivot=str[i]; while(i<j){ while(i<j&&str[j]>=pivot){ j--; } if(i<j){ str[i]=str[j]; i++; } while(i<j&&str[i]<=pivot){ i++; } if(i<j){ str[j]=str[i]; j--; } } str[i]=pivot; return i; } void quickSort(char *arr,int start,int end){ int i; if(start<end){ i=partition(arr,start,end); quickSort(arr,start,i-1); quickSort(arr,i+1,end); } } ///比较排序之后的两个字符串,进行线性扫描,时间复杂度为O(m+n) int isContainByMethod2(char* strLong,char* strShort){ int nLong=strlen(strLong); int nShort=strlen(strShort); ///如果有重复的字母,排序是什么一种情况 int i,j; for(i=0;i<=nLong-nShort;i++){ if(strLong[i]==strShort[0]){ for(j=0;j<=nShort-1;j++){ if(strLong[i+j]!=strShort[j]){ break; } } if(j==nShort){ cout<<"true"<<endl; return 1; } } } cout<<"flase"<<endl; return 0; } /******************方法三,同方法二,只不过排序采用的是线性排序法,这里采用计数排序的方法,排序O(n+m),线性扫描o(m+n);一共o(m+n)+o(m+n)=o(m+n)********/ void countSort(char* str){ int help[26]={0}; int len=strlen(str); char* help_str=new char[len]; int i,n,pos; for(i=0;i<len;i++){ n=str[i]-'a'; help[n]++; } for(i=1;i<26;i++){ help[i]+=help[i-1]; } //将每个元素放在合适的位置 for(i=len;i>=0;i--){ n=str[i]-'a'; pos=help[n]-1; help_str[pos]=str[i]; //这个是考虑原来数组可能有相同元素 help[n]--; } for(i=0;i<len;i++){ str[i]=help_str[i]; } delete[] help_str; }
3,采用hash的方法(如果引入大素数,倒确实是一种有趣的思路,其中牵涉到大数除法,这里暂略)
void isContain(string str_long,string str_short){ int hash[26]={0}; int len_short=str_short.length(); int len_long=str_long.length(); for(int i=0;i<len_short;i++){ int index=str_short[i]-'a'; hash[index]=1; } for(int i=0;i<len_long;i++){ int index=str_long[i]-'a'; if(hash[index]==1){ hash[index]=0; } } int i=0; for(i=0;i<26;i++){ if(hash[i]==1){ break; } } if(i==26){ cout<<"true"<<endl; }else{ cout<<"false"<<endl; } }
4,采用hash结合bitmap的方法
采用位的思想来构建辅助数组,可以大大降低控件复杂度,这中思想在解决内存有限的去重、查找、排序问题中很常见
void isContainByHashAndBitmap(string str_long,string str_short){ int len_short=str_short.length(); int len_long=str_long.length(); //char 在内存中占据一个字节的存储空间 //这里需要借助4个字节,不过这里不再借助char[4]数组了,而是采用一个int类型 //char *hash=new char[4]; int dictionary=0; for(int i=0;i<len_long;i++){ dictionary|=getBit(str_long[i]); } int i; for(i=0;i<len_short;i++){ if(dictionary!=(dictionary|getBit(str_short[i]))){ break; } } if(i==len_short){ cout<<"true"<<endl; }else{ cout<<"false"<<endl; } }
5,判断两个字符串是否匹配,采用hash的方法
//判断字符串是否匹配,即两个字符串含有的字符个数相同,只是顺序不同 //这里对于字符串包含重复字符的情况也考虑在内了 bool is_match(const char* strOne,const char* strTwo){ int lenOfOne=strlen(strOne); int lenOfTwo=strlen(strTwo); if(lenOfOne!=lenOfTwo){ return false; } int hash[26]={0}; for(int i=0;i<lenOfOne;i++){ int index=strOne[i]-'a'; hash[index]++; } for(int i=0;i<lenOfTwo;i++){ int index=strTwo[i]-'a'; if(hash[index]!=0){ hash[index]--; }else{ return false; } } return true; }
6,查找一个字符串中子字符串的位置
#include<iostream> #include<cstring> using namespace std; int main(){ string str_long="abcdef"; string str_short="cd"; int findsubstring(string,string); int result=findsubstring(str_long,str_short); cout<<result<<endl; return 0; } //查找子字符串的位置,还可以考虑排序之后再查找,或者通过KMP算法,其复杂度为O(m+n) int findsubstring(string str_long,string str_short){ int len_long=str_long.length(); int len_short=str_short.length(); for(int i=0;i<len_long-len_short;i++){ if(str_long[i]==str_short[0]){ int j; for( j=0;j<len_short;j++){ if(str_long[i+j]!=str_short[j]){ break; } } if(j==len_short){ cout<<"true"<<endl; return i; } } } cout<<"false"<<endl; return 0; }
7,找出一个字符串中第一次只出现一次的字符
同样利用hash的方法,
#include<iostream> #include<cstring> using namespace std; int main(){ string str="eeooidjia"; char find_first_unique_char(string); cout<<find_first_unique_char(str)<<endl; return 0; } char find_first_unique_char(string str){ int hash[26]={0}; int len=str.length(); for(int i=0;i<len;i++){ int index=str[i]-'a'; hash[index]++; } for(int i=0;i<len;i++){ int index=str[i]-'a'; if(hash[index]==1){ return str[i]; } } return '/0'; }
8,将字符串转换为对应的数字输出,如“-12635”转换为-12635输出
(1) char转换为int的方法 -'0'
(2) '+'、'-'的符号考虑
(3) 大型数字溢出的考虑
#include<iostream> #include<cstring> #include<assert.h> using namespace std; //实现功能,将字符串转换为对应的数字,如“123”输出数字123 //需要注意的问题 //1,如果使用的指针,需要判断指针是否为空 //2,如果输入的字符串中包含有不是数字的字符,那么碰到这些非法字符的是偶,给出出错信息,判断数字是否在‘0’到‘9’之间 //3,数字可能还包含“+”,“-”号,利用一个符号位来记录该信息 //4,如果输入的是字符串形式的数字,可能存在溢出的可能,判断溢出的标志是在处理符号之前判断是否>0 int main(){ string str="-23876"; int str_2_int(string); cout<<str_2_int(str)<<endl; return 0; } int str_2_int(string str){ assert(str.length()>0); int pos=0; int sys=1; if(str[pos]=='+'){ sys=1; pos++; }else if(str[pos]=='-'){ sys=-1; pos++; } int num=0; for(pos;pos<str.length();pos++){ assert(str[pos]>='0'); assert(str[pos]<='9'); num=num*10+(str[pos]-'0'); //处理溢出 assert(num>=0); } num*=sys; return num; }
9,字符串的复制操作,主要看考虑是否周全
#include<iostream> #include<cstring> #include<assert.h> using namespace std; //实现功能,将字符串转换为对应的数字,如“123”输出数字123 //需要注意的问题 //1,如果使用的指针,需要判断指针是否为空 //2,如果输入的字符串中包含有不是数字的字符,那么碰到这些非法字符的是偶,给出出错信息,判断数字是否在‘0’到‘9’之间 //3,数字可能还包含“+”,“-”号,利用一个符号位来记录该信息 //4,如果输入的是字符串形式的数字,可能存在溢出的可能,判断溢出的标志是在处理符号之前判断是否>0 int main(){ string str="-23876"; int str_2_int(string); cout<<str_2_int(str)<<endl; return 0; } int str_2_int(string str){ assert(str.length()>0); int pos=0; int sys=1; if(str[pos]=='+'){ sys=1; pos++; }else if(str[pos]=='-'){ sys=-1; pos++; } int num=0; for(pos;pos<str.length();pos++){ assert(str[pos]>='0'); assert(str[pos]<='9'); num=num*10+(str[pos]-'0'); //处理溢出 assert(num>=0); } num*=sys; return num; }
10,找出N(可以是个海量数据)个数中最小的K个数
思路:取出N个数中开始的k个数字构成一个初始的最大堆
遍历k到N中间的数字,和heap[0]比较,如果比heap[0]小,那么就替换heap[0],重新调整这个最大堆
在文档上还有快速排序达到O(n)复杂度的讨论,没怎么看明白,有时间再研究吧
#include<iostream> #include<cstring> using namespace std; void swap(int* a,int* b){ if(*a!=*b){ *a=*a^*b; *b=*a^*b; *a=*a^*b; } } int leftChildPos(int pos){ return (pos<<1)+1;//注意位移的优先级小于+,因此这里的括号一定不能省 } int rightChildPos(int pos){ return (pos+1)<<1; } int parent(int pos){ return (pos-1)>>1; } bool isLeaf(int pos,int MAXLEN){ return pos>=(MAXLEN>>1)&&pos<MAXLEN; } void adjustHeap(int array[],int i,int len){ int largeIndex=i; int left=leftChildPos(i); int right=rightChildPos(i); if(left<len&&array[largeIndex]<array[left]){ largeIndex=left; } if(right<len&&array[largeIndex]<array[right]){ largeIndex=right; } if(largeIndex!=i){ swap(array[i],array[largeIndex]); adjustHeap(array,largeIndex,len); } } void buildHeap(int array[],int len){ if(array==NULL){return;} int i; for(i=len/2-1;i>=0;i--){ adjustHeap(array,i,len); } } int main(){ int MAXLEN=10000; int data[10000]={0}; int i=0; for(i=0;i<MAXLEN;i++){ data[i]=MAXLEN-i; } int k=10; int heap[10]={0}; for(int i=0;i<k;i++){ heap[i]=data[i]; } cout<<"排序之前的数组为:"<<endl; for(int i=0;i<10;i++){ cout<<heap[i]<<" "; } cout<<endl; buildHeap(heap,k); cout<<"堆初始化之后的数组:"<<endl; for(int i=0;i<10;i++){ cout<<heap[i]<<" "; } cout<<endl; int newData=-1; for(int i=10;i<MAXLEN;i++){ newData=data[i]; if(newData<heap[0]){ heap[0]=newData; adjustHeap(heap,0,k); } } cout<<"建立起来的最大堆为:"<<endl; for(int i=0;i<k;i++){ cout<<heap[i]<<" "; } cout<<endl; //备注:建立起来最大堆,得到了最小的k个数,如果想要顺序排列的话,采用堆排序即可 return 0; }
11.Top K问题
问题10的对立面,在实际中非常有用,即寻找最大的k个数字的问题,可以采用的算法有,这在自己的论文个性化搜索中可以体现,
比如,统计搜索引擎中的热门查询词汇
(这一点可以运用到论文的个性化搜索模块,如何将大众总体的热门搜索和用户的搜索历史融合起来,提供个性化服务,这一点值得考虑,也可以作为自己对后台的贡献部分)
所谓hashTable,就是把关键码值(key value)直接进行访问的方法,把关键码值映射到表中一个位置的方法叫做散列,存放记录的数组就叫做散列表。
哈希表hashtable(key,value) 的做法其实很简单,就是把Key通过一个固定的算法函数既所谓的哈希函数转换成一个整型数字,然后就将该数字对数组长度进行取余,取余结果就当作数组的下标,将value存储在以该数字为下标的数组空间里。
而当使用哈希表进行查询的时候,就是再次使用哈希函数将key转换为对应的数组下标,并定位到该空间获取value,如此一来,就可以充分利用到数组的定位性能进行数据定位(文章第二、三部分,会针对Hash表详细阐述)。在下面从数量庞大的字符串数组中,判断某个特定的字符串是否存在的问题中,会用到这一点
因此对于这个问题:
第一步:query统计
维护一个key为query字符串,value为该字符串出现次数的hashtable(利用java里面内置的hashtable数据结构的话,相当于字符串映射为地址码的这一步,有系统帮助完成了)
第二步:top10问题解决
维护一个大小为10的最小堆,和解决找出最小的k个值的思路是一样的。
12,和上面一道题目的关系是同样涉及了hashtable的概念,判断一个字符串时候存在于一个很大的字符串数组中
#include<iostream> using namespace std; //问题描述,有一个庞大的字符串数组,给一个单独的字符串,让你从这个字符串数组中判断是否有这个字符串并找到它 //思路:第一步:通过某种算法,将以个字符串“压缩”成一个整数,当然,无论如何,一个32位的整数是无法对应回一个字符串的(,这就是one-way hash算法的思想,这也说明funf的加密方法是不可逆的?),不过在程序中,两个字符串计算出hash值相等的可能性非常小 //第二步:通常的想法会是,逐个比较字符串的hash数值,但这种解决方案远远不够,要想得到最快的算法,就不能进行逐个的比较,通常是构造一个哈希表来解决问题,哈西表的大小根据程序进行定义,例如1024,每一个hash值通过取模运算对应到数组中的一个位置,这样,只要比较这个字符串的的哈希值对应的位置有没有占用,就可以得到最后的结果,这样事件复杂度就是O(1)<不过,最初大数组生成hash,确定对应位置是否占用,不是依旧需要遍历一次么?> //可能的问题是“两个字符串在哈希表中对应的位置相同怎么办”,解决方法很多,一种方法就是“链表” //但是一个更绝的方法是,其基本原理是,在哈希表中不是用一个哈希值而是用三个哈希值来校验字符串,一个用于哈西表的下标,另外两个用来验证 //总上,算法的流程为: //1,计算出字符串的三个哈希值 //2,察看哈希表中的这个位置 //3,哈希表中该位置是否为空,如果为空,那么该字符串不存在,返回-1 //4,如果存在,那么检查其他两个哈希值是否也匹配,如果匹配,表示找到了该字符串,返回该hash值 //5,移到下一个位置,如果已经移动到了表的末尾,就反转到表的开头继续查询 //6,看看是不是回到原来位置,如果是,就返回没有找到 //7,回到3 //下面的代码仅是测试:准备hash表,并将一个字符串映射为一个数值 #include <stdio.h> #include <ctype.h> //多谢citylove指正。 //crytTable[]里面保存的是HashString函数里面将会用到的一些数据,在prepareCryptTable //函数里面初始化 unsigned long cryptTable[0x500]; //以下的函数生成一个长度为0x500(合10进制数:1280)的cryptTable[0x500] void prepareCryptTable() { unsigned long seed = 0x00100001, index1 = 0, index2 = 0, i; for( index1 = 0; index1 < 0x100; index1++ ) { for( index2 = index1, i = 0; i < 5; i++, index2 += 0x100 ) { unsigned long temp1, temp2; seed = (seed * 125 + 3) % 0x2AAAAB; temp1 = (seed & 0xFFFF) << 0x10; seed = (seed * 125 + 3) % 0x2AAAAB; temp2 = (seed & 0xFFFF); cryptTable[index2] = ( temp1 | temp2 ); } } } //以下函数计算lpszFileName 字符串的hash值,其中dwHashType 为hash的类型, //在下面GetHashTablePos函数里面调用本函数,其可以取的值为0、1、2;该函数 //返回lpszFileName 字符串的hash值; unsigned long HashString( char *lpszFileName, unsigned long dwHashType ) { unsigned char *key = (unsigned char *)lpszFileName; unsigned long seed1 = 0x7FED7FED; unsigned long seed2 = 0xEEEEEEEE; int ch; while( *key != 0 ) { ch = toupper(*key++); seed1 = cryptTable[(dwHashType << 8) + ch] ^ (seed1 + seed2); seed2 = ch + seed1 + seed2 + (seed2 << 5) + 3; } return seed1; } //在main中测试argv[1]的三个hash值: //./hash "arr/units.dat" //./hash "unit/neutral/acritter.grp" int main( int argc, char **argv ) { unsigned long ulHashValue; int i = 0; if ( argc != 2 ) { printf("please input two arguments/n"); return -1; } /*初始化数组:crytTable[0x500]*/ prepareCryptTable(); /*打印数组crytTable[0x500]里面的值*/ for ( ; i < 0x500; i++ ) { if ( i % 10 == 0 ) { printf("\n"); } printf("%-12X", cryptTable[i] ); } ulHashValue = HashString( argv[1], 0 ); printf("\n----%X ----\n", ulHashValue ); ulHashValue = HashString( argv[1], 1 ); printf("----%X ----\n", ulHashValue ); ulHashValue = HashString( argv[1], 2 ); printf("----%X ----\n", ulHashValue ); return 0; } //如果要完整实现上面的代码: typedef struct { int nHashA; int nHashB; char bExists; ...... } SOMESTRUCTRUE; int GetHashTablePos( har *lpszString, SOMESTRUCTURE *lpTable ) //lpszString要在Hash表中查找的字符串,lpTable为存储字符串Hash值的Hash表。 { int nHash = HashString(lpszString); //调用上述函数二,返回要查找字符串lpszString的Hash值。 int nHashPos = nHash % nTableSize; if ( lpTable[nHashPos].bExists && !strcmp( lpTable[nHashPos].pString, lpszString ) ) { //如果找到的Hash值在表中存在,且要查找的字符串与表中对应位置的字符串相同, return nHashPos; //则返回上述调用函数二后,找到的Hash值 } else { return -1; } }
13,libFunction
#include<stdio.h> #include<stdlib.h> #include<assert.h> //copy the first n char to a destStr char *strncpy(char* strDes,const char *strSrc,unsigned int count){ assert(strDes!=NULL&&strSrc!=NULL); char *address=strDes; while(count--&&*strSrc!='\0'){ *strDes++=*strSrc++; } *strDes='\0'; return address; } int main(){ char* strSrc="dsdfjeoi"; char* strDes=(char*)malloc(sizeof(char)*2); strncpy(strDes,strSrc,10); printf("%s\n",strSrc); printf("%s\n",strDes); printf("%d\n",sizeof(strDes)); free(strDes); return 0; }
int strcmp(const char* str1,const char* str2){ assert(str1!=NULL&&str2!=NULL); while(*str1&&*str2&&*str1++==*str2++); return(*str1-*str2); //while(*str1&&*str2){ //str1++; //str2++; //} //return(*str1-*str2); }
char* strcat(char* strDes,const char* strSrc){ assert(strDes!=NULL&&strSrc!=NULL); char *address=strDes; while(*strDes){strDes++;}; while(*strSrc){*strDes++=*strSrc++;}; *strDes='\0'; return address; }
//从一个长字符串中需找子字符串,如果存在,返回子字符串第一次出现的位置 //const 只能赋值给const变量 //const变量可以通过强制类型转换为对应的非const变量 char *strstr(const char* str_long,const char* str_short){ assert(str_long!=NULL&&str_short!=NULL); const char* s; const char* t; const char* str; for(s=str_long;*s!='\0';s++){ for(str=s,t=str_short;*t!='\0'&&*str==*t;t++,str++){ } if(*t=='\0'){ return (char*)s; } } return NULL; }
//依次检验字符串s1中的字符,当该字符在字符串s2中也包含的时候,就停止检验 char* strpbrk(const char* str1,const char* str2){ const char* s; const char* t; for(s=str1;*s!='\0';s++){ for(t=str2;*t!='\0';t++){ if(*s==*t){ return (char*)s; } } } return NULL; }
//依次检验字符串s1中的字符,当该字符在字符串s2中也包含的时候,就停止检验 char* strpbrk(const char* str1,const char* str2){ const char* s; const char* t; for(s=str1;*s!='\0';s++){ for(t=str2;*t!='\0';t++){ if(*s==*t){ return (char*)s; } } } return NULL; }
//顺序在字符串s1中寻找与s2中字符的第一个相同字符,返回这个字符在s1中第一次出现的 位置 int strcspn(const char* str1,const char* str2){ assert(str1!=NULL&&str2!=NULL); const char* s; const char* t; for(s=str1;*s!='\0';s++){ for(t=str2;*t!='\0';t++){ if(*s==*t){ return s-str1; } } } return -1; }
//依次检验字符串s1中的字符,当该字符在字符串s2中也包含的时候,就停止检验 char* strpbrk(const char* str1,const char* str2){ const char* s; const char* t; for(s=str1;*s!='\0';s++){ for(t=str2;*t!='\0';t++){ if(*s==*t){ return (char*)s; } } } return NULL; }
//查找一个字符c在字符串str中末次出现的位置,也就是从str的右侧开始找,字符c首次出 现的位置,并且返回从字符串的这个位置起,一直到字符串结束的所有字符,如果没有找到 指定的字符,那么返回NULL char* strrchar(const char* str,char c){ const char* s; for(s=str;*s!='\0';s++){ } for(--s;*s!=c;s--){ if(s==str){return NULL;} } return (char*)s; }
//将字符串中所有字符的顺序颠倒过来,这里进行的是原地修改 char* strrev( char *str){ assert(str!=NULL); char* s=str; char* t; while(*s!='\0'){s++;} // printf("%c\n",*(--s)); char c; //奇怪,为什么t!=s就不行呢? for(t=str,--s;t<s;t++,s--){ c=*s; *s=*t; *t=c; } return str; }
//将一个字符串中前count个字符都设置为指定字符c char* strnset(char* str,char c,unsigned int count){ assert(str!=NULL); char* s; for(s=str;count>0&&*str!='\0';count--,s++){ *s=c; } return str; }
//拷贝src所指的内存内容的前n个字节到dest所指向的内存地址,同strcpy不同的地方在于 memcpy用来复制完整的n个字节,不会因为碰到‘\0’而结束 //const 修饰指针的几种情形,A:const在前面,const char*p;那么*p即p的内容是const,p指针本身可以改变。const char * const p;指针p和内容*p都不可以改变。B:const在后面,char const *p;*p即p的内容是const,指针p可以改变,char* const p,那么指针p>是const,*p即p的内容可以改变;char const * const p;p指针和p的内容都不可以改变。 规律是,以*进行分界,如果const在左侧,那么*p即p的内容可以改变,而指针不变。如果const在右侧,那么指针不可改变,但是指针指向的内容可以改变 void* memcpy(void* dest,const void* src,unsigned int n){ void* address=dest; while(n--){ *(char*)dest=*(char*)src; dest=(char*)dest+1; src=(char*)src+1; } return address; }
(上个例子中提高了const指针的一些用法)
字符串中字符的大小写转换
//将字符串中的小写字母转换为大写形式,其他的字符不变 char* strupr(char* str){ assert(str!=NULL); char* tmp=str; for(tmp=str;*tmp!='\0';tmp++){ if(*tmp>'a'&&*tmp<'z'){ *tmp=*tmp+'A'-'a'; } } return str; } //将字符串中的大写字母转换为小写形式,其他的字符保持不变 char* strlwr(char* str){ assert(str!=NULL); char * tmp; for(tmp=str;*tmp!='\0';tmp++){ if(*tmp>'A'&&*tmp<'Z'){ *tmp=*tmp+'a'-'A'; } } return str; }
14,二分查找法的实现
(每一次都是利用middle处的数值和待比较的数字进行比较)
//问题描述 //输入一个数组和一个数字,在数组中查找两个数,使得它们的和正好是输入的那个数字,>要求时间复杂度为O(n);例如,输入数字1、2、4、7、11、15和数字15.那么输出4、11 //在解决这个问题之前,首先实现一下二分查找方法 //n为数组长度,v为要寻找的数字,二分查找的前提是数组为有序数组 int binary_search(int array[],int n,int v){ int left=0; int right=n-1; int middle; while(left<=right){ middle=left+(right-left)/2; if(array[middle]<v){ left=middle+1; } else if(array[middle]>v){ right=middle-1; } else{ return middle; } } return -1; }