day9|151.翻转字符串里的单词 kmp-爱代码爱编程
151.翻转字符串里的单词
识别
这段代码实现了三个字符串处理功能:reverse
用于翻转字符串中指定范围的字符;removeExtraSpace
用于删除字符串两端和中间多余的空格;reverseWords
用于翻转字符串中的单词顺序。
核心/易错
核心功能是字符串的翻转和处理空格。易错点包括:
- 确保字符串翻转时索引不越界。
- 正确处理字符串中的空格,避免删除或翻转时出错。
难点/亮点
- 难点:正确处理字符串中的空格,特别是在单词之间和字符串两端的空格。
- 亮点:
reverseWords
函数通过先删除多余空格再翻转单词顺序的方式,实现了单词顺序的翻转,同时保持了单词内部字符的顺序。
算法设计思路
reverse
:通过两个指针从两端向中间遍历,交换字符直到两个指针相遇或交错。removeExtraSpace
:首先找到字符串的非空格起始和结束位置,然后遍历字符串,跳过连续的空格,只保留单个空格。reverseWords
:先调用removeExtraSpace
函数去除多余空格,然后翻转整个字符串,最后遍历翻转后的字符串,翻转每个单词。
代码实现
#include <stdio.h>
#include <stdlib.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;
}
}
// 删除字符串两端和中间多余的空格
void removeExtraSpace(char* s) {
int start = 0; // 指向字符串开头的指针
int end = strlen(s) - 1; // 指向字符串结尾的指针
while (s[start] == ' ') start++; // 移动指针 start,直到找到第一个非空格字符
while (s[end] == ' ') end--; // 移动指针 end,直到找到第一个非空格字符
int slow = 0; // 指向新字符串的下一个写入位置的指针
for (int i = start; i <= end; i++) { // 遍历整个字符串
if (s[i] == ' ' && s[i+1] == ' ') { // 如果当前字符是空格,并且下一个字符也是空格,则跳过
continue;
}
s[slow] = s[i]; // 否则,将当前字符复制到新字符串的 slow 位置
slow++; // 将 slow 指针向后移动
}
s[slow] = '\0'; // 在新字符串的末尾添加一个空字符
}
// 翻转字符串中的单词
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; // 返回处理后的字符串
}
// 主函数,用于测试上述函数
int main() {
char s[] = " hello world ";
printf("Original: '%s'\n", s);
char *result = reverseWords(s);
printf("Reversed: '%s'\n", result);
return 0;
}
KMP
识别
实现了字符串匹配的两种算法:简单匹配(暴力匹配)和KMP算法。它定义了一个结构体 StrType
来存储字符串及其长度,并提供了相应的函数来分配字符串、执行简单匹配和KMP匹配。
核心/易错
核心功能是字符串匹配,易错点包括内存管理(如正确分配和释放内存)、边界条件处理(如字符串的起始和结束)以及KMP算法中next数组的正确计算。
难点/亮点
- 难点:KMP算法的next数组计算是理解上的难点,需要正确处理子字符串的前缀和后缀匹配。
- 亮点:代码结构清晰,将字符串处理和算法逻辑分开,易于理解和维护。
算法设计思路
- 简单匹配:通过两个索引分别遍历主字符串和子字符串,逐个字符比较,不匹配则子字符串从头开始重新匹配。
- KMP算法:通过预处理子字符串生成next数组,该数组用于在不匹配时调整子字符串的起始比较位置,避免从头开始。(next数组的核心遇见了冲突的位置要向前回退
子串的最长相等前后缀的长度s[i]!= s[j]即前缀末尾和后缀末尾不匹配的时候
j为他的前一位的next数组的值同时它也表示前缀的末尾
B站代码随想录next数组代码17min48)
代码实现
#include <stdio.h>
#include <stdlib.h>
// 定义结构体,用于存储字符串及其长度
typedef struct {
char *str;
int length;
} StrType;
// 函数原型声明
int strAssign(StrType *str, const char *ch); // 字符串分配函数
int index_simple(const StrType *str, const StrType *subStr); // 简单匹配函数
int indexKMP(const StrType *str, const StrType *subStr, const int next[]); // KMP匹配函数
void getNext(const StrType *subStr, int next[]); // 计算next数组函数
// strAssign 函数实现,分配内存并复制字符串
int strAssign(StrType* str, const char* ch) {
if (str->str) {
free(str->str); // 释放原有内存
}
int len = 0;
while (ch[len]) {
++len;
}
if (len == 0) {
str->str = NULL;
str->length = 0;
} else {
str->str = malloc(sizeof(char) * (len + 2)); // 分配内存,+2是为了存储'\0'和可能的扩展
if (!str->str) {
return 0; // 内存分配失败
}
str->str[0] = '\0'; // 0号索引填充'\0'
for (int i = 1; i <= len; ++i) {
str->str[i] = ch[i - 1];
}
str->length = len;
}
return len;
}
// 简单匹配(暴力匹配)函数实现
int index_simple(const StrType* str, const StrType* subStr) {
int i = 1; // 主字符串索引
int j = 1; // 子字符串索引
while (i <= str->length && j <= subStr->length) {
if (str->str[i] == subStr->str[j]) {
++i;
++j;
} else {
j = 1; // 重置子字符串索引
i = (j > 0) ? i + 1 : 1; // 调整主字符串索引
}
}
if (j > subStr->length) {
return i - subStr->length; // 匹配成功,返回位置
}
return 0; // 匹配失败,返回0
}
// KMP算法的next数组计算函数实现
void getNext(const StrType* subStr, int next[]) {
int i = 1;
int j = 0;
next[1] = 0; // 初始化
while (i < subStr->length) {
if (subStr->str[i] == subStr->str[j]) {
++i;
++j;
next[i] = j;
} else if (j > 0) {
j = next[j];
} else {
++i;
}
}
}
// KMP算法函数实现
int indexKMP(const StrType* str, const StrType* subStr, const int next[]) {
int i = 1;
int j = 1;
while (i <= str->length && j <= subStr->length) {
if (j == 0 || str->str[i] == subStr->str[j]) {
++i;
++j;
} else {
j = next[j]; // 根据next数组调整j
}
}
if (j > subStr->length) {
return i - subStr->length; // 匹配成功,返回位置
}
return 0; // 匹配失败,返回0
}
// main 函数实现
int main() {
StrType str, subStr;
int next[100]; // 假设next数组足够大
// 为str和subStr分配字符串
strAssign(&str, "这是一个示例字符串");
strAssign(&subStr, "示例");
// 计算next数组
getNext(&subStr, next);
// 使用简单匹配查找子字符串
int simpleIndex = index_simple(&str, &subStr);
printf("简单匹配找到子字符串的位置: %d\n", simpleIndex);
// 使用KMP算法查找子字符串
int kmpIndex = indexKMP(&str, &subStr, next);
printf("KMP算法找到子字符串的位置: %d\n", kmpIndex);
// 释放分配的内存
free(str.str);
free(subStr.str);
return 0;
}
每行代码都添加了注释,以便更好地理解其功能和逻辑。
●卡码网:55.右旋转字符串
●28. 实现 strStr()
●459.重复的子字符串
●字符串总结