先附上地址方便对照着看:代码随想录
四、字符串
C语言中字符串通常是常量字符串或字符数组,可以用数组下标读取字符串的某一字符
常用库函数整理:
strlen(str):返回str字符串的字符长度,返回结果为 unsigned int
strcpy(str1,str2):将 str2 的复制到 str1 中去,注意不会检查 str1 是否够长,所以需要注意 str1 的长度是否能够容纳,str2 可以是字符数组也可以是字符串常量。
strncpy(str1,str2,n):将 str2 的前n个字符复制到 str1 ,会检查 str1 容量
strcmp(str1,str2):比较两个字符串的ASCII码大小,若出现不相同的字符,则以第一对不相同的字符比较结果为准,相等时返回0
strncmp(str1,str2,n):比较两个字符串前n个字符的ASCII码大小,返回值同上
strcat(str1,str2):将 str2 连接到 str1 的后面,结果保存在 str1 中,也要注意 str1 的长度
strstr(str1,str2):判断 str2 是否是 str1 的子串,如果是,返回 str2 在 str1 中首次出现的地址,否则返回NULL
strlwr(str):将 str 中的大写字母转为小写字母
strupr(str):将str中的小写字母转为大写字母
以上库函数都在string.h头文件中,dev c++可以ctrl加鼠标左键点击头文件进入,然后用ctrl + F搜索函数则可以看到函数需要传入的参数值,机试的时候很好用。
1、反转字符串:没啥好说的,直接莽就完了,注意字符串末尾是以 '\0' 结尾的,别把这个也反转了就行
void reverseString(char* s, int sSize) {
char tmp;
for(int i=0;i<sSize/2;i++){
tmp = s[sSize-i-1];
s[sSize-i-1] = s[i];
s[i] = tmp;
}
}
2、反转字符串II:思维有点僵化了,看到这种一段一段的处理应该就要想到改变for循环的变量处理,i++用多了确实会让人习惯啊。
基本思路:题目每2k个字符判断一次,那么在循环体里应该是 i+=(2*k),然后再根据题意判断情况,如果剩余字符少于k,那么反转右边界应该是 strlen(s)-1,否则就是 i+k-1
步骤如下:
step 1:设置外层for循环,注意 i+=(2*k)
step 2:设置反转两端,left 始终是 i ,但 right 需要根据剩余字符数是否小于 k 来判断
step 3:设置内部 while 循环用于反转字符串,反转思路同上一题,用 for 循环也可以,但是没有while 循环这么好的易读性
char* reverseStr(char* s, int k) {
int left,right;
for(int i=0;i<strlen(s);i+=(2*k)){ //step 1
if(strlen(s)-i<k) // step 2
right = strlen(s)-1;
else right = i+k-1;
left = i;
while(left < right){ //step 3
char tmp = s[left];
s[left++] = s[right];
s[right--] = tmp;
}
}
return s;
}
3、替换数字:看完代码随想录的思路感觉不太适合机试,机试不需要这么多技巧,能够暴力就暴力,就算因为暴力解法扣掉一两个测试点的分也无伤大雅,考试最重要的就是用最短的时间拿到最多的分数,完全没必要追求极致的解法。
所以我选择暴力解法:创建一个足够大的新数组,遍历题目给的数组,遇到小写字母就直接存进去,遇见数字就用 strcat 函数拼上 number 字符串,我的机试建议是:能多用库函数就用库函数,不然机试的时候根本想不起来就麻烦了。注意结尾添一个 '\0' ,否则无法打印。
#include <stdio.h>
#include <string.h>
int main(){
char s[10000];
char res[100000];
int cnt=0;
scanf("%s",&s);
for(int i=0;i<strlen(s);i++){
if('a'<= s[i] && s[i] <= 'z')
res[cnt++]=s[i];
else if('0'<=s[i] && s[i]<='9'){
strcat(res,"number");
cnt += 6;
}
}
res[cnt]='\0';
printf("%s",res);
return 0;
}
4、反转字符串中的单词:这道题很好,但不适合机试,因为机试采用的是输入输出的形式,所以用简单的 scanf 外面套一层循环其实就能去除字符串前后和中间的多余空格,然后存入一个二维字符串数组直接倒序输出,输出的时候每个单词中间输出一个空格就好。
但这道题还是很好的,可以学习一下如何处理多余空格。
理一下这道题的完整思路:首先去除多余空格,然后将整个字符串进行翻转,再依次翻转其中的每个单词。
去除多余空格步骤如下:
step 1:设置头指针和尾指针分别指向字符串开头和结尾,注意尾指针指向是 '\0'前面那个字符
step 2:分别移动头尾指针,找到第一个不是空格的字符,这一步其实就是去除两端多余空格
step 3:设定一个写入指针并从头指针所指位置开始遍历,直至尾指针,其中要注意去除中间多余空格。如何去除:注意到中间的多余空格一定是≥2个,所以当前字符如果是空格且下一个字符也是空格时就不予理会,直到当前是空格但下一个字符不是空格时再存入空格。
step 4:新写入的字符串尾部添加 '\0'
// 删除字符串两端和中间多余的空格
void removeExtraSpace(char* s) {
int start = 0; //step 1
int end = strlen(s) - 1;
while (s[start] == ' ') start++; //step 2
while (s[end] == ' ') end--;
int slow = 0; // 指向新字符串的下一个写入位置的指针
for (int i = start; i <= end; i++) { //step 3
if (s[i] == ' ' && s[i+1] == ' ') { // 如果当前字符是空格,并且下一个字符也是空格,则跳过
continue;
}
s[slow] = s[i]; // 否则,将当前字符复制到新字符串的 slow 位置
slow++; // 将 slow 指针向后移动
}
s[slow] = '\0'; //step 4
}
反转部分只要设置一个函数,传入待反转的字符串,反转起始位置和结束位置就行
// 翻转字符串中指定范围的字符
void reverse(char* s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
int tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}
最后实现题目要求的方法,注释很详细就不多赘述了
// 翻转字符串中的单词
char * reverseWords(char * s){
removeExtraSpace(s); // 先删除字符串两端和中间的多余空格
reverse(s, 0, strlen(s) - 1); // 翻转整个字符串
int slow = 0; // 指向每个单词的开头位置的指针
for (int i = 0; i <= strlen(s); i++) { // 遍历整个字符串
if (s[i] ==' ' || s[i] == '\0') { // 如果当前字符是空格或空字符,说明一个单词结束了
reverse(s, slow, i-1); // 翻转单词
slow = i + 1; // 将 slow 指针指向下一个单词的开头位置
}
}
return s; // 返回处理后的字符串
}
5、右旋字符串:
如果不限制空间复杂度的话,这道题我有两个思路:一个正儿八经的做一个新字符串,一个直接取巧输出
如果限制空间复杂度不能申请额外空间的话:就是代码随想录的思路和取巧输出
先看第一种:解法是先申请一个额外字符串,然后复制原本字符串的最后 n 个字符到新字符串的开头,然后利用 strcat 库函数将原来字符串的前 n 个拼接到新字符串后面,这种解法适合力扣这种需要返回值的刷题平台。
#include <stdio.h>
#include <string.h>
int main(){
int num,i;
char str[10000],res[10000];
scanf("%d",&num);
scanf("%s",&str);
for(i=0;i<num;i++){
res[i] = str[strlen(str)-num+i]; //将后n个字符放到新字符串开头
}
res[i]='\0';
str[strlen(str)-num]='\0'; //注意截断原来的字符串再拼接
strcat(res,str); //拼接
printf("%s",res);
return 0;
}
第二种就是直接取巧输出,先输出后n个字符,再输出前面 strlen(str)-n 个字符,适用于机试或一些给定输入让你输出的平台
#include <stdio.h>
#include <string.h>
int main(){
int num,i;
char str[10000];
scanf("%d",&num);
scanf("%s",&str);
for(i=0;i<num;i++)
printf("%c",str[strlen(str)-num+i]);
for(i=0;i<strlen(str)-num;i++)
printf("%c",str[i]);
return 0;
}
如果限制空间复杂度为O(1)的话,那就等同于2010年的408算法题最优解
思路也很简单:先反转整个字符串,再分别反转前 n 个字符和后 strlen(str)-n 个字符
演示如下:设 n 为 2
abcdefg -> gfedcba -> fgedcba -> fgabcde,三次反转即可,由于C语言没有现成的反转函数,上一题中的reverse函数就可以拿来用
#include <stdio.h>
#include <string.h>
// 翻转字符串中指定范围的字符
void reverse(char* s, int start, int end) {
for (int i = start, j = end; i < j; i++, j--) {
int tmp = s[i];
s[i] = s[j];
s[j] = tmp;
}
}
int main(){
int num,i;
char str[10000];
scanf("%d",&num);
scanf("%s",&str);
reverse(str,0,strlen(str)-1);
reverse(str,0,num-1);
reverse(str,num,strlen(str)-1);
printf("%s",str);
return 0;
}
6、实现strStr():应付机试的话没必要手搓KMP算法,虽然它相对其他来说比较好写,也不需要暴力逐个字符进行比对,只需要用到库函数 strncmp 就好,代码非常简单,一看就会。
也是暴力的一种思路,但无需逐个字符比对,机试的时候多利用库函数总是没错的。
int strStr(char* haystack, char* needle) {
int length=strlen(needle);
for(int i=0;i<strlen(haystack);i++)
{
if(strncmp(haystack+i,needle,length)==0)
return i;
}
return -1;
}
7、重复的子字符串:我的建议是直接暴力,尽量不要去手搓算法,这对C语言一点也不友好,对于机试的话你算法如果写错了调试都很麻烦。
bool repeatedSubstringPattern(char* s) {
int n = strlen(s);
for (int i = 1; i * 2 <= n; ++i) {
if (n % i == 0) {
bool match = true;
for (int j = i; j < n; ++j) {
if (s[j] != s[j - i]) {
match = false;
break;
}
}
if (match) {
return true;
}
}
}
return false;
}
总结:字符串类型的题目多用库函数,等到想学算法的再来手写算法也不迟。