字符串笔试题
1、字符串移位包含问题
//普通解法 bool contain_check() { char s[6] = "AABCD"; char d[5] = "CDAA"; int len = strlen(s); for(int i=0; i<len; ++i) { char temp = s[0]; for(int j=0; j<len-1; ++j) s[j] = s[j+1]; s[len-1] = temp; if(strstr(s,d) != 0) return true; } return false; }
寻找规律:对S1做循环移位所得到的字符串都是字符串S1S1的子字符串,如果S2可以由S1循环移位得到,那么S2一定在S1S1上。
字符串循环同构问题:如果字符串s1可以经过有限次循环得到s2,则称s1和s2是循环同构的。S=s1+s1为主串,s2为模式串。如果s1和s2是循环同构的,那么s2就一定可以在S中找到匹配!
2、求一个字符串中出现频率最高的那个字符及其出现次数
空间换时间:使用一个额外的数组统计每个字符出现的次数,再扫描一次得到查找的字符,这是O(N)的时间复杂度。得到字符串第一个无重复的字符也可以用这种方法。
假设处理的是ASCII字符集:需要注意 char的范围是[-128,127]之间,所以数组下标要加上128
void get_most(char *s, char &ch, int &size) { ch = '\0'; size = 0; if (NULL != s) { int n[256]; memset(n, 0, sizeof(n)); while(*s != '\0') { n[*s+128] += 1; if((n[*s+128]) > size) { size = n[*s+128]; ch = *s; } s++; } } }
3、给一个字符串,有大小写字母,要求写一个函数把小写字母放在前面,大写字母放在后面,尽量使用最小的空间、时间复杂度。
void move_char(char* a) { char* i = a; char* j = a+strlen(a)-1; while(i < j) { while(*i && (*i>='a' && *i<='z')) ++i; if((*i) == '\0') break; while(*j>='A' && *j<='Z') --j; if(i < j) { char c = *i; *i = *j; *j = c; } ++i; --j; } }
4、编写字符串处理函数,将字符串中的字符'*'移到串的前部分,前面的非'*'字符后移,但不能改变非'*'字符的先后顺序,函数返回串中字符'*'的数量。如原始串为:ab**cd**e*12,处理后为*****abcde12,函数并返回值为5。(要求使用尽量少的时间和辅助空间)
int partitionStar(char a[],int len) { int count = 0; int i = len-1; int j = len-1; //j指向第一个'*' while(i >= 0) { if (a[i] != '*') { swap(a[i--], a[j--]); } else { i--; count++; } } return count; }
5、删除字符串中的数字并压缩字符串。如字符串"abc123de4fg56"处理后变为"abcdefg"。注意空间和效率。
(下面的算法只需要一次遍历,不需要开辟新空间,时间复杂度为O(N))
char* delete_digits(char* str) { char* i = str; // i for cursor, j for the first digit char; char* j = str; while(*i != '\0') { if (*i<'0' || *i>'9') { *j++ = *i++; } else { ++i; } } *j ='\0'; return str; }
6、删除特定字符:写一个高效的函数,删除字符串里给定的字符
void Remove_chars(char str[], char remove[]) { int remove_arr[256]; for(int i=0; i<256; ++i) remove_arr[i] = 0; for(int i=0; remove[i]; ++i) remove_arr[remove[i]] = 1; int i = 0; for(int j=0; str[j]; ++j) { if(!remove_arr[str[j]]) str[i++] = str[j]; } str[i]='\0'; }
7、编码实现字符串转整型的函数(实现函数atoi的功能)。如将字符串“123”转化为123,“-0123”转化为-123
int str_to_int(const char* str) { int is_neg = 0, num = 0; const char * p = str; if (*str == '-') { is_neg = 1; ++ p; } while(*p >= '0' && *p <= '9') { num = num * 10 + (*p-'0'); ++ p; } if(is_neg) num *= -1; return num; }
编码实现整型转字符串的函数
//整数最大的位数 #define MAX_DIGITS_NUM 10 void int_to_str(int num,char str[]) { int i = 0, j = 0, is_neg = 0; char temp[MAX_DIGITS_NUM+2]; if(num < 0) { num *= -1; is_neg = -1; } //使用do while循环可以处理num为0的情况 do { temp[i++] = (num%10)+'0'; num /= 10; }while(num); if(is_neg) temp[i++] = '-'; while(i > 0) str[j++] = temp[--i]; str[j] = '\0'; }
8、翻转句子中单词的顺序
题目:输入一个英文句子,翻转句子中单词的顺序,但单词内字符的顺序不变。句子中单词以空格符隔开。为简单起见,标点符号和普通字母一样处理。例如输入“I am a student.”,则输出“student. a am I”。 分析:先颠倒句子中的所有字符,再颠倒每个单词内的字符。由于单词内的字符被翻转两次,因此顺序仍然和输入时的顺序保持一致。
void Reverse(char *pBegin, char *pEnd) { if(pBegin == NULL || pEnd == NULL) return; while(pBegin < pEnd) { char temp = *pBegin; *pBegin = *pEnd; *pEnd = temp; pBegin ++, pEnd --; } } char* ReverseSentence(char *pData) { if(pData == NULL) return NULL; char *pBegin = pData; char *pEnd = pData; while(*pEnd != '\0') pEnd ++; pEnd--; // Reverse the whole sentence Reverse(pBegin, pEnd); // Reverse every word in the sentence pBegin = pEnd = pData; while(*pBegin != '\0') { if(*pBegin == ' ') { pBegin ++; pEnd ++; continue; } // A word is between with pBegin and pEnd, reverse it else if(*pEnd == ' ' || *pEnd == '\0') { Reverse(pBegin, --pEnd); pBegin = ++pEnd; } else { pEnd ++; } } return pData; }
9、求字符串的最长重复子串:构造字符串的后缀数组,对后缀数组排序,再两两比较得到最长的重复子串
//compare funciton used by qsort() int pstrcmp(const void *p, const void *q) { return strcmp(*(char **)p, *(char **)q); } //get max common length of string p and q int comlen(char *p, char *q) { int i = 0; while (*p && (*p++ == *q++)) i++; return i; } //get max repeat substring of str int find_max_repeat(char* str, char* result, int & len) { int temlen, maxi, maxlen = -1; char *a[99999]; int n = 0; while (*str != '\0') { a[n++] = str++; } qsort(a, n, sizeof(char *), pstrcmp); for (int i = 0; i < n-1; i++) { temlen = comlen(a[i], a[i+1]); if (temlen > maxlen) { maxlen = temlen; maxi = i; } } result = a[maxi]; len = maxlen; printf("%.*s\n", maxlen, result); return maxlen; }
10、字符串字母包含问题:有一个各种字母组成的字符串,还有一个字母数目较少的字符串,什么方法能最快的查出所有小字符串里的字母在大字符串里都有?
最简单的方法:轮询小字符串里的每个字母,看它是否同在第一个字符串里。这需要O(n*m)次操作,其中n是string1的长度,m是string2的长度。
一个改进的方法:对这两个字符串的字母进行排序,然后同时对两个字串依次轮询。两个字串的排序需要(常规情况)O(mlogm)+ O(nlogn)次操作,之后的线性扫描需要O(m+n)次操作。(随着字串长度的增长,你会发现这个算法的效果会越来越好)
一个很好的方法:只需要O(n+m)次操作。对第一个长字串进行轮询,把其中的每个字母都放入一个Hashtable里(成本是O(n)次操作)。然后轮询第二个字串,在Hashtable里查询每个字母,看能否找到,如果找不到,说明没有匹配成功,这将消耗掉O(m)次操作。
一个更有趣的方法:对一个一定个数的字母组成的字串,给每个字母分配一个素数,从2开始,往后类推。这样A将会是2,B将会是3,C将会是5,等等。现在先遍历第一个字串,把每个字母代表的素数相乘,会得到一个很大的整数。然后——轮询第二个字符串,用每个字母代表的素数除它。如果除的结果有余数,这说明有不匹配的字母,如果整个过程中没有余数,你应该知道它是第一个字串恰好的子集了。
11、求一个字符串的最长的没有重复字符的子串。
方法一:穷举法,使用2重外循环遍历所有的区间,用2重内循环检验子串是否符合“无重复字符”这一要求。其中外层循环i、j 遍历所有的下标,m、n是内层循环,检查区间[i,j]是否符合要求。空间复杂度是O(1),时间复杂度O(N^4)。
方法二:对方法一的检验子串是否“无重复字符”进行改进,使用hash表记录字符是否出现过。
方法三:使用DP,对于最长不重复子串,某个当前的字符,如果它与前面的最长不重复子串中的字符没有重复,那么就可以以它为结尾构成新的最长子串;如果有重复,那么就与某个稍短的子串构成新的子串或者单独成一个新子串。
方法四:对这个字符串构造后缀数组,在每个后缀数组中,寻找没有重复字符的最长前缀,就是要找的子串。
详解:http://www.cnblogs.com/luxiaoxun/archive/2012/10/02/2710471.html
12、求一个字符串中连续出现次数最多的子串
int count = 0; char sub_str[256]; void find_str(char *str) { int str_len = strlen(str); int i, j, k; int tmp_cnt = 0; for(i = 0; i < str_len; i++) { for(j = i+1; j < str_len; j++) { int n = j-i; //sub string length tmp_cnt = 1; if(strncmp(&str[i], &str[j], n) == 0) //compare n-lengths strings { tmp_cnt++; //they are equal, so add count for(k = j+n; k < str_len; k += n) //consecutive checking { if(strncmp(&str[i], &str[k], n) == 0) { tmp_cnt++; } else break; } if(count < tmp_cnt) { count = tmp_cnt; memcpy(sub_str, &str[i], n); //record the sub string } } } } }
13、寻找包含给定字符集合的最短子串:字符串S="abcdefg",字符集合D={'c','f'},那这个最小子串为S'="cdef"。
//判断hash是否包含所有的hash_sub int has_sub(int * hash, int * hash_sub) { for(int i=0; i<256; ++i) { if(hash_sub[i] && !hash[i]) return 0; } return 1; } //在str中寻找包含dst的最短子串 int min_substring(char * str, char * dst) { char * begin = str; char * end = str; char * begin_index = NULL; int minlen = strlen(str); int hash[256]; int hash_sub[256]; memset(hash,0,sizeof(hash)); memset(hash_sub,0,sizeof(hash_sub)); for(int i=0; dst[i]; ++i) hash_sub[dst[i]] = 1; hash[*begin] = 1; while(*end) { while(!has_sub(hash,hash_sub) && *(end+1)) { ++ end; hash[*end] = 1; } while(has_sub(hash,hash_sub)) { if(end-begin+1 < minlen) { minlen = end-begin+1; begin_index = begin; } if (*begin != *(begin+1)) { hash[*begin] = 0; } //hash[*begin] = 0; ++ begin; } if(*(end+1) == '\0') break; } printf("%.*s\n", minlen, begin_index); return minlen; }
14、递归求解一个字符串中连续单个字符出现最多次数字符的个数
int max_count; void count(const char *s) { if(!(*s)) return; const char * p = s+1; int n = 1; while(*p && *p == *s) { ++ n; ++ p; } if(n > max_count) max_count = n; count(s+1); }