学校算法作业 (六)——— 子串
哎,这次ACM的题有些复杂,毕竟字符串题还是有不小难度的,我们来看一下题.(评测机没开,没图了,将就着看看吧)
问题描述
现在有一些由英文字符组成的大小写敏感的字符串,
你的任务是找到一个最长的字符串x,
使得对于已经给出的字符串中的任意一个y,x或者是y的子串,
或者x中的字符反序之后得到的新字符串是y的子串。
输入数据
输入的第一行是一个整数t (1 <= t <= 10),t表示测试数据的数目。
对于每一组测试数据,第一行是一个整数n (1 <= n <= 100),
表示已经给出n个字符串。接下来n行,每行给出一个长度在1和100之间的字符串。
输出要求
对于每一组测试数据,输出一行,给出题目中要求的字符串x的长度。
输入样例
2
3
ABCD
BCDFF
BRCD
2
rose
orchid
输出样例
2
2
考察的属于最长子序列的查找,这道题没什么特别恶心人的地方,暴力就好了。或者优化一下匹配函数。下面我说一下思路。
1、找出长度最小的字串。(最长也不会最小的输入字串长)
2、反转最短字串
3、找出最大相同字串
思路大概就这三步,实现起来,还是有点意思的。
第一步,可以在输入的时候完成,设置一个min变量;
第二步,可以写个函数实现,也可以直接调用 strrev (自己查怎么用);
第三步,先选定想用的模式匹配函数,(kmp或者Strstr或者其它)然后,用两重for循环控制字符长度以及正反向递减,再嵌入一层循环控制字符串与模式串之间的比较,完事儿。
讲的有点抽象,为了先打一个大的框架,这也是我的编程思维——自顶向下,逐步细化。
下面贴一下第三步代码,略微讲一下,没啥技巧,纯暴力
1 /** 2 * @brief 查找最大子串 3 * @note 4 * @param *Original: 最短的输入字符串 5 * @param ArrayStrSize: 字符串数组大小 6 * @retval 字串长度 7 * @author 杨文蓁的小迷弟 8 */ 9 int SearchMax(char *Original, int ArrayStrSize) 10 { 11 bool flag; //崩坏标志 12 int len = strlen(Original); 13 char Normal[MAXSIZE]; 14 char Reversed[MAXSIZE]; //用来存储逆序 15 16 //比较次数 17 for (int i = len; i >= 1; i--) 18 { 19 //从第j处开始取字串 20 for (int j = 0; j <= len - i; j++) 21 { 22 strncpy(Normal, Original + j, i); 23 strncpy(Reversed, Original + j, i); 24 Normal[i] = '\0'; 25 Reversed[i] = '\0'; 26 strrev(Reversed); 27 //reverse(Reversed);//备用翻转函数 28 29 flag = 1; 30 31 for (int row = 0; row < ArrayStrSize; row++) 32 { 33 if (-1 == KMP(ArrayStr[row], Normal) && -1 == KMP(ArrayStr[row], Reversed)) 34 { 35 flag = 0; 36 break; 37 } 38 } 39 if (flag) 40 { 41 return i; 42 } 43 } 44 } 45 return 0; 46 }
正如我上面所说的,三重for,两重控制strncpy来改变模式串长度及减小的方向,一个控制比较的主串的变化。
if (-1 == KMP(ArrayStr[row], Normal) && -1 == KMP(ArrayStr[row], Reversed))
这里等于-1 是由于我写的kmp匹配失败返回的为-1。匹配成功的话,由于我的最外层for从len-1开始,所以直接就是最长,不成功的话继续匹配。
最后我贴一下最终的代码
1 /** 2 * @brief 字串 3 * @note strrevn函数只在gcc中可用Linux和cb都是gcc的解释器 4 * @author 杨文蓁的小迷弟 5 */ 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <stdbool.h> 10 #define MAXSIZE 101 11 12 char ArrayStr[MAXSIZE][MAXSIZE]; 13 14 void GetNext(char *Pattern, int next[]); 15 int KMP(char *MainString, char *Pattern); 16 int SearchMax(char *Original, int ArrayStrSize); 17 void strrevn(char *str1, char *str2); 18 void reverse(char *str); 19 20 int main() 21 { 22 int times; 23 int ArrayStrSize; 24 25 scanf("%d", ×); 26 int index = 0; 27 for (int i = 0; i < times; i++) 28 { 29 scanf("%d", &ArrayStrSize); 30 int min = MAXSIZE; 31 for (int j = 0; j < ArrayStrSize; j++) 32 { 33 scanf("%s", ArrayStr[j]); 34 if (min > strlen(ArrayStr[j])) 35 { 36 min = strlen(ArrayStr[j]); 37 index = j; 38 } 39 } 40 41 printf("%d\n", SearchMax(ArrayStr[index], ArrayStrSize)); 42 } 43 return 0; 44 } 45 /** 46 * @brief 查找最大子串 47 * @note 48 * @param *Original: 最短的输入字符串 49 * @param ArrayStrSize: 字符串数组大小 50 * @retval 字串长度 51 */ 52 int SearchMax(char *Original, int ArrayStrSize) 53 { 54 bool flag; //崩坏标志 55 int len = strlen(Original); 56 char Normal[MAXSIZE]; 57 char Reversed[MAXSIZE]; //用来存储逆序 58 59 //比较次数 60 for (int i = len; i >= 1; i--) 61 { 62 //从第j处开始取字串 63 for (int j = 0; j <= len - i; j++) 64 { 65 strncpy(Normal, Original + j, i); 66 strncpy(Reversed, Original + j, i); 67 Normal[i] = '\0'; 68 Reversed[i] = '\0'; 69 strrev(Reversed); 70 //reverse(Reversed);//备用翻转函数 71 72 flag = 1; 73 74 for (int row = 0; row < ArrayStrSize; row++) 75 { 76 if (-1 == KMP(ArrayStr[row], Normal) && -1 == KMP(ArrayStr[row], Reversed)) 77 { 78 flag = 0; 79 break; 80 } 81 } 82 if (flag) 83 { 84 return i; 85 } 86 } 87 } 88 return 0; 89 } 90 91 /** 92 * @brief 字符串反转函数 93 * @note 备用 94 * @param *str: 要反转的字符串 95 * @retval None 96 */ 97 void reverse(char *str) 98 { 99 int len = strlen(str); 100 101 for (int i = 0; i < len / 2; i++) 102 { 103 char temp = str[i]; 104 str[i] = str[len - i - 1]; 105 str[len - i - 1] = temp; 106 } 107 } 108 109 /** 110 * @brief 字符串反转函数(二) 111 * @note 这种更像接口函数 112 * @param *str1: 被反转的字符串 113 * @param *str2: 反转后存储的地方 114 * @retval None 115 */ 116 void strrevn(char *str1, char *str2) 117 { 118 int l; 119 int i; 120 121 l = strlen(str1); 122 for (i = 0; i < l; i++) 123 { 124 str2[i] = str1[l - 1 - i]; 125 } 126 } 127 128 /** 129 * @brief KMP(字串查找) 130 * @note 匹配失败返回-1 131 * @param *MainString: 主串 132 * @param *Pattern: 模式串 133 * @retval 最长匹配字串的第一次出现的第一个字母的位置 134 */ 135 int KMP(char *MainString, char *Pattern) 136 { 137 int next[MAXSIZE] = {0}; 138 GetNext(Pattern, next); 139 140 int i = 0; 141 int j = 0; 142 int s_len = strlen(MainString); 143 int PatternLen = strlen(Pattern); 144 145 while (i < s_len && j < PatternLen) 146 { 147 if (j == -1 || MainString[i] == Pattern[j]) 148 { 149 i++; 150 j++; 151 } 152 else 153 { 154 j = next[j]; 155 } 156 } 157 158 if (j == PatternLen) 159 { 160 return i - j; 161 } 162 163 return -1; 164 } 165 166 /** 167 * @brief 生成转跳数组(未优化) 168 * @note 不要使用优化版 169 * @param *Pattern: 模式串 170 * @param next[]: 转跳数组 171 * @retval None 172 */ 173 void GetNext(char *Pattern, int next[]) 174 { 175 int PatternLen = strlen(Pattern); 176 int i = 0; 177 int j = -1; 178 next[0] = -1; 179 180 while (i < PatternLen - 1) 181 { 182 if (j == -1 || Pattern[i] == Pattern[j]) 183 { 184 i++; 185 j++; 186 next[i] = j; 187 } 188 else 189 { 190 j = next[j]; 191 } 192 } 193 }
再贴一个Strstr款的
1 /** 2 * @brief 子串(纯调用string库版本) 3 * @note strrevn函数只在gcc中可用Linux和cb都是gcc的解释器 4 * @author 杨文蓁的小迷弟 5 */ 6 #include <stdio.h> 7 #include <stdlib.h> 8 #include <string.h> 9 #include <stdbool.h> 10 #define MAXSIZE 101 11 12 char ArrayStr[MAXSIZE][MAXSIZE]; 13 14 void GetNext(char *Pattern, int next[]); 15 int KMP(char *MainString, char *Pattern); 16 int SearchMax(char *Original, int ArrayStrSize); 17 void strrevn(char *str1, char *str2); 18 void reverse(char *str); 19 20 int main() 21 { 22 int times; 23 int ArrayStrSize; 24 25 scanf("%d", ×); 26 int index = 0; 27 for (int i = 0; i < times; i++) 28 { 29 scanf("%d", &ArrayStrSize); 30 int min = MAXSIZE; 31 for (int j = 0; j < ArrayStrSize; j++) 32 { 33 scanf("%s", ArrayStr[j]); 34 if (min > strlen(ArrayStr[j])) 35 { 36 min = strlen(ArrayStr[j]); 37 index = j; 38 } 39 } 40 41 printf("%d\n", SearchMax(ArrayStr[index], ArrayStrSize)); 42 } 43 return 0; 44 } 45 /** 46 * @brief 查找最大子串 47 * @note 48 * @param *Original: 最短的输入字符串 49 * @param ArrayStrSize: 字符串数组大小 50 * @retval 字串长度 51 */ 52 int SearchMax(char *Original, int ArrayStrSize) 53 { 54 bool flag; //崩坏标志 55 int len = strlen(Original); 56 char Normal[MAXSIZE]; 57 char Reversed[MAXSIZE]; //用来存储逆序 58 59 //比较次数 60 for (int i = len; i >= 1; i--) 61 { 62 //从第j处开始取字串 63 for (int j = 0; j <= len - i; j++) 64 { 65 strncpy(Normal, Original + j, i); 66 strncpy(Reversed, Original + j, i); 67 Normal[i] = '\0'; 68 Reversed[i] = '\0'; 69 strrev(Reversed); 70 //reverse(Reversed);//备用翻转函数 71 72 flag = 1; 73 74 for (int row = 0; row < ArrayStrSize; row++) 75 { 76 if (NULL == strstr(ArrayStr[row], Normal) && NULL == strstr(ArrayStr[row], Reversed)) 77 { 78 flag = 0; 79 break; 80 } 81 } 82 if (flag) 83 { 84 return i; 85 } 86 } 87 } 88 return 0; 89 }
如果strrev不可用,那自行写一个反转函数。
更新日期 2019-10-11
拿c++实现了一下,感兴趣的可以看看,用的是c++11的标准,除了功能函数之外,基本用自带函数实现。
1 #include <iostream> 2 #include <vector> 3 #include <String> 4 #include <algorithm> 5 6 class SubString 7 { 8 public: 9 int SearchMax(std::vector<std::string> StrArray, int Index); 10 }; 11 12 int main() 13 { 14 using std::cin; 15 using std::cout; 16 using std::string; 17 using std::vector; 18 19 int times, index = 0; 20 int StrArraySize; 21 cin >> times; 22 for (int i = 0; i < times; ++i) 23 { 24 vector<string> StrArray; 25 cin >> StrArraySize; 26 int min = 101; 27 for (int j = 0; j < StrArraySize; ++j) 28 { 29 string InputStrtemp; 30 cin >> InputStrtemp; 31 StrArray.push_back(InputStrtemp); 32 if (min > StrArray[j].size()) 33 { 34 min = StrArray[j].size(); 35 index = j; 36 } 37 } 38 SubString SUBSTR; 39 int result = SUBSTR.SearchMax(StrArray, index); 40 cout << result << '\n'; 41 StrArray.clear(); 42 } 43 return 0; 44 } 45 46 int SubString::SearchMax(std::vector<std::string> StrArray, int Index) 47 { 48 bool Flag = true; 49 int StrArrayLen = StrArray.size(); 50 int MinStrLen = StrArray[Index].size(); 51 std::string NormalStr, ReverseStr; 52 53 for (int i = MinStrLen; i >= 1; --i) 54 { 55 for (int j = 0; j <= MinStrLen - i; ++j) 56 { 57 Flag = true; 58 NormalStr = StrArray[Index].substr(j, i); 59 ReverseStr = NormalStr; 60 reverse(ReverseStr.begin(), ReverseStr.end()); 61 62 for (int k = 0; k < StrArrayLen; ++k) 63 { 64 if (std::string::npos == StrArray[k].find(NormalStr) && std::string::npos == StrArray[k].find(ReverseStr)) 65 { 66 Flag = false; 67 break; 68 } 69 } 70 if (Flag) 71 { 72 return i; 73 } 74 } 75 76 } 77 return 0; 78
1 #include <iostream> 2 #include <vector> 3 #include <string> 4 #include <algorithm> 5 6 class SubString 7 { 8 public: 9 int SearchMax(std::vector<std::string> StrArray, int Index); 10 }; 11 12 int main() 13 { 14 using std::cin; 15 using std::cout; 16 using std::string; 17 using std::vector; 18 19 int times, index = 0; 20 int StrArraySize; 21 cin >> times; 22 for (int i = 0; i < times; ++i) 23 { 24 vector<string> StrArray; 25 cin >> StrArraySize; 26 int min = 101; 27 for (int j = 0; j < StrArraySize; ++j) 28 { 29 string InputStrtemp; 30 cin >> InputStrtemp; 31 StrArray.push_back(InputStrtemp); 32 if (min > StrArray[j].size()) 33 { 34 min = StrArray[j].size(); 35 index = j; 36 } 37 } 38 SubString SUBSTR; 39 int result = SUBSTR.SearchMax(StrArray, index); 40 cout << result << '\n'; 41 StrArray.clear(); 42 } 43 return 0; 44 } 45 46 int SubString::SearchMax(std::vector<std::string> StrArray, int Index) 47 { 48 bool Flag = true; 49 int StrArrayLen = StrArray.size(); 50 int MinStrLen = StrArray[Index].size(); 51 std::string NormalStr, ReverseStr; 52 53 for (int i = MinStrLen; i >= 1; --i) 54 { 55 for (int j = 0; j <= MinStrLen - i; ++j) 56 { 57 Flag = true; 58 NormalStr = StrArray[Index].substr(j, i); 59 ReverseStr = NormalStr; 60 reverse(ReverseStr.begin(), ReverseStr.end()); 61 62 for (int k = 0; k < StrArrayLen; ++k) 63 { 64 if (std::string::npos == StrArray[k].find(NormalStr) && std::string::npos == StrArray[k].find(ReverseStr)) 65 { 66 Flag = false; 67 break; 68 } 69 } 70 if (Flag) 71 { 72 return i; 73 } 74 } 75 76 } 77 return 0; 78 }
算法不易,诸君共勉!