LeetCode字符串相关算法题(1)【C语言版】

520. 检测大写字母

我们定义,在以下情况时,单词的大写用法是正确的:

  • 全部字母都是大写,比如 "USA"
  • 单词中所有字母都不是大写,比如 "leetcode"
  • 如果单词不只含有一个字母,只有首字母大写, 比如 "Google"

给你一个字符串 word 。如果大写用法正确,返回 true ;否则,返回 false

示例 1:

复制代码
输入:word = "USA"
输出:true

示例 2:

复制代码
输入:word = "FlaG"
输出:false

提示:

  • 1 <= word.length <= 100
  • word 由小写和大写英文字母组成
cpp 复制代码
#include <cstdio>

// 判断一个字符是否是大写字母
bool judgeBig(char c) {
    // 判断字符c是否在'A'到'Z'之间
    return c >= 'A' && c <= 'Z';
}

// 检查整个单词是否全部由大写字母组成
bool allBig(char* word) {
    int i = 0;
    int flag = 0; // 用于统计大写字母的数量
    while (word[i] != '\0') { // 遍历整个单词
        if (judgeBig(word[i])) { // 如果当前字符是大写字母
            flag++; // 统计大写字母数量
        }
        i++; // 移动到下一个字符
    }
    // 如果大写字母的数量等于单词的长度,说明全部是大写字母
    return flag == i;
}

// 检查整个单词是否全部由小写字母组成
bool allSmall(char* word) {
    int i = 0;
    int flag = 0; // 用于统计小写字母的数量
    while (word[i] != '\0') { // 遍历整个单词
        if (!judgeBig(word[i])) { // 如果当前字符不是大写字母,即为小写字母
            flag++; // 统计小写字母数量
        }
        i++; // 移动到下一个字符
    }
    // 如果小写字母的数量等于单词的长度,说明全部是小写字母
    return flag == i;
}

// 检查单词是否首字母大写,其余字母小写
bool hasSmallAndBig(char* word) {
    int i = 0;
    int flag = 0; // 用于统计小写字母的数量
    if (judgeBig(word[0])) { // 检查首字母是否是大写字母
        i++; // 移动到第二个字符
        flag++; // 首字母是大写,统计小写字母数量从1开始
        while (word[i] != '\0') { // 遍历剩余字符
            if (!judgeBig(word[i])) { // 如果当前字符是小写字母
                flag++; // 统计小写字母数量
            }
            i++; // 移动到下一个字符
        }
    } else {
        // 首字母不是大写字母,直接返回false
        return false;
    }
    // 如果小写字母的数量加上首字母等于单词的长度,说明符合条件
    return i == flag;
}

// 综合判断单词的大小写使用是否符合规则
bool detectCapitalUse(char* word) {
    // 如果全部是大写字母,返回true
    if (allBig(word)) {
        return true;
    }
    // 如果全部是小写字母,返回true
    if (allSmall(word)) {
        return true;
    }
    // 如果首字母大写,其余小写,返回true
    return hasSmallAndBig(word);
}

int main() {
    char* word = {"Google"}; // 测试单词
    if (detectCapitalUse(word)) { // 调用函数判断
        puts("true"); // 如果符合条件,输出true
    } else {
        puts("false"); // 如果不符合条件,输出false
    }
}

大佬写的

cpp 复制代码
#include <cstdio>        // 包含标准输入输出库,用于printf、puts等函数
#include <cctype>        // 包含字符处理函数,如isupper
#include <cstring>       // 包含字符串处理函数,如strlen

bool detectCapitalUse(char* word) {
    int flag = 0;        // 用于统计大写字母的数量
    int i = 0;           // 循环计数器
    while (word[i] != '\0') { // 遍历单词中的每个字符,直到字符串结束符
        if (isupper(word[i])) { // 检查当前字符是否为大写字母
            flag++;             // 如果是大写字母,增加计数器
        }
        i++;                   // 移动到下一个字符
    }
    // 判断是否符合三种情况之一:
    // 1. 全小写:flag == 0
    // 2. 全大写:flag == strlen(word)
    // 3. 首字母大写:flag == 1 且首字母是大写
    return flag == 0 || flag == strlen(word) || (flag == 1 && isupper(word[0]));
}

int main() {
    char* word = {"Google"}; // 定义并初始化测试单词
    if (detectCapitalUse(word)) { // 调用函数判断大小写规则
        puts("true");             // 如果符合条件,输出"true"
    } else {
        puts("false");            // 如果不符合条件,输出"false"
    }
}

125. 验证回文串

如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串

字母和数字都属于字母数字字符。

给你一个字符串 s,如果它是 回文串 ,返回 true;否则,返回false

示例 1:

复制代码
输入: s = "A man, a plan, a canal: Panama"
输出:true
解释:"amanaplanacanalpanama" 是回文串。

示例 2:

复制代码
输入:s = "race a car"
输出:false
解释:"raceacar" 不是回文串。

示例 3:

复制代码
输入:s = " "
输出:true
解释:在移除非字母数字字符之后,s 是一个空字符串 "" 。
由于空字符串正着反着读都一样,所以是回文串。
cpp 复制代码
#include <cstdio>    // 提供输入输出函数,如printf和scanf
#include <cstdlib>   // 提供内存管理函数,如malloc和free
#include <cstring>   // 提供字符串操作函数,如strlen和strcpy
#include <cctype>    // 提供字符分类函数,如isdigit和isalpha

/**
 * 判断一个字符串是否是回文
 */
bool isPalindrome(char* s) {
    if (s == NULL) {
        return false; // 处理空指针情况
    }

    int len = strlen(s); // 获取字符串的长度
    if (len == 0) {
        return true; // 空字符串视为回文
    }

    // 分配内存以存储处理后的字符串
    char* temp = (char*)malloc(len * sizeof(char));
    if (temp == NULL) {
        return false; // 内存分配失败
    }

    int j = 0; // 用于跟踪处理后字符串的长度
    for (int i = 0; i < len; ++i) {
        char c = s[i];
        if (isdigit(c) || isalpha(c)) { // 检查是否是数字或字母
            if (isalpha(c)) {
                temp[j++] = tolower(c); // 将字母转换为小写并存入temp
            } else {
                temp[j++] = c; // 将数字直接存入temp
            }
        }
    }

    // 如果处理后的字符串为空,视为回文
    if (j == 0) {
        free(temp); // 释放内存
        return true;
    }
    //temp[j] = '\0'; // 添加字符串结束符

    // 检查回文
    int left = 0;
    int right = j - 1;
    while (left < right) {
        if (temp[left] != temp[right]) { // 如果左右字符不匹配
            free(temp); // 释放内存
            return false; // 不是回文
        }
        left++; // 左指针右移
        right--; // 右指针左移
    }

    free(temp); // 释放内存
    return true; // 是回文
}

int main() {
    char* s = "a"; // 定义一个字符串
    if (isPalindrome(s)) { // 判断是否是回文
        printf("true\n"); // 如果是回文,输出true
    } else {
        printf("false\n"); // 如果不是回文,输出false
    }
    return 0; // 程序结束
}

14. 最长公共前缀

编写一个函数来查找字符串数组中的最长公共前缀。

如果不存在公共前缀,返回空字符串 ""

示例 1:

复制代码
输入:strs = ["flower","flow","flight"]
输出:"fl"

示例 2:

复制代码
输入:strs = ["dog","racecar","car"]
输出:""
解释:输入不存在公共前缀。

提示:

  • 1 <= strs.length <= 200
  • 0 <= strs[i].length <= 200
  • strs[i] 如果非空,则仅由小写英文字母组成
cpp 复制代码
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <cctype>

// 函数:计算字符串数组的最长公共前缀
char* longestCommonPrefix(char** strs, int strsSize) {
    if (strsSize == 0) return "";  // 如果字符串数组为空,返回空字符串

    // 获取第一个字符串的长度,作为初始最小长度
    int minLength = strlen(strs[0]);  
    for (int i = 0; i < strsSize; ++i) {
        int currentLength = strlen(strs[i]);  // 获取当前字符串的长度
        if (currentLength < minLength) {
            minLength = currentLength;  // 更新最小长度
        }
    }

    // 创建一个字符数组,用于存储最长公共前缀
    // 需要分配 minLength + 1 的空间,以便存储字符串的终止符 '\0'
    char* ans = (char*)malloc((minLength + 1) * sizeof(char)); 
    memset(ans, '\0', minLength + 1);  // 初始化数组为全零

    // 遍历每个字符位置,直到最小长度
    for (int i = 0; i < minLength; ++i) {
        char currentChar = strs[0][i];  // 当前比较的字符(从第一个字符串中取)
        for (int j = 0; j < strsSize; ++j) {
            if (currentChar != strs[j][i]) {  // 如果当前字符在某个字符串中不匹配
                return ans;  // 返回当前已匹配的公共前缀
            }
        }
        ans[i] = currentChar;  // 如果所有字符串在该位置字符相同,则将其加入结果
    }

    // 如果所有字符串完全相同,返回完整的公共前缀
    return ans;
}

int main() {
    // 定义字符串数组
    const char* strings[] = {"aaaa", "aaaa", "aaaa"};
    int numStrings = sizeof(strings) / sizeof(strings[0]);  // 计算字符串数组的大小
    int maxLen = 200;  // 每个字符串的最大长度(假设值)

    // 分配内存,用于存储字符串数组
    char** strs = (char**)malloc(numStrings * sizeof(char*));
    for (int i = 0; i < numStrings; i++) {
        strs[i] = (char*)malloc(maxLen * sizeof(char));  // 为每个字符串分配内存
        strcpy(strs[i], strings[i]);  // 复制字符串内容
    }

    // 调用函数计算最长公共前缀
    char* ans = longestCommonPrefix(strs, numStrings);

    // 打印结果
    printf("%s\n", ans);

    // 释放动态分配的内存
    for (int i = 0; i < numStrings; i++) {
        free(strs[i]);  // 释放每个字符串的内存
    }
    free(strs);  // 释放字符串数组的内存
    free(ans);  // 释放最长公共前缀的内存

    return 0;
}

344. 反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须**原地修改输入数组**、使用 O(1) 的额外空间解决这一问题。

示例 1:

复制代码
输入:s = ["h","e","l","l","o"]
输出:["o","l","l","e","h"]

示例 2:

复制代码
输入:s = ["H","a","n","n","a","h"]
输出:["h","a","n","n","a","H"]
cpp 复制代码
#include <cstdio>
#include <cstring>

void reverseString(char* s, int sSize) {
    size_t half = sSize / 2;
    for (int i = 0; i < half; i++) {
        char temp = s[i];
        s[i] = s[sSize - i - 1];
        s[sSize - i - 1] = temp;
    }
}


int main() {
    char s[] = {'h','e','l','l','o'};
    int length = sizeof(s) / sizeof(s[0]);
    reverseString(s,length);
    for (int i = 0; i < length; ++i) {
        printf("%c ",s[i]);
    }
}

541. 反转字符串 II

给定一个字符串 s 和一个整数 k,从字符串开头算起,每计数至 2k 个字符,就反转这 2k 字符中的前 k 个字符。

  • 如果剩余字符少于 k 个,则将剩余字符全部反转。
  • 如果剩余字符小于 2k 但大于或等于 k 个,则反转前 k 个字符,其余字符保持原样。

示例 1:

复制代码
输入:s = "abcdefg", k = 2
输出:"bacdfeg"

示例 2:

复制代码
输入:s = "abcd", k = 2
输出:"bacd"
cpp 复制代码
#include <cstdio>
#include <cstring>
#include <cstdlib>

// 反转字符串中从start位置开始的sSize个字符
void reverseString(char* s, int start, int sSize) {
    int half = sSize / 2; // 计算需要交换的次数
    for (int i = 0; i < half; i++) {
        char temp = s[start + i]; // 临时存储当前字符
        s[start + i] = s[start + sSize - i - 1]; // 将当前字符替换为对应位置的字符
        s[start + sSize - i - 1] = temp; // 将对应位置的字符替换为临时存储的字符
    }
}

// 根据给定的整数k,反转字符串中的字符
char* reverseStr(char* s, int k) {
    int length = strlen(s); // 计算字符串的长度

    int n = length / (2 * k); // 计算可以分成多少个完整的2k块
    int last = length % (2 * k); // 计算剩余的字符数

    // 处理每个完整的2k块
    for (int i = 0; i < n; ++i) {
        reverseString(s, i * 2 * k, k); // 反转当前块的前k个字符
    }

    // 处理剩余的字符
    if (last > 0) {
        if (last >= k) {
            reverseString(s, n * 2 * k, k); // 如果剩余字符数大于等于k,反转前k个字符
        } else {
            reverseString(s, n * 2 * k, last); // 如果剩余字符数小于k,反转全部剩余字符
        }
    }

    return s; // 返回处理后的字符串
}

int main() {
    char s[] = {'a','b','c','d','e','f','g'}; // 定义一个字符数组
    char * ans = reverseStr(s, 2); // 调用reverseStr函数,k=2
    for (int i = 0; i < strlen(ans); ++i) {
        printf("%c ", ans[i]); // 打印处理后的字符串
    }
    return 0;
}

557. 反转字符串中的单词 III

给定一个字符串 s ,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

示例 1:

复制代码
输入:s = "Let's take LeetCode contest"
输出:"s'teL ekat edoCteeL tsetnoc"

示例 2:

复制代码
输入: s = "Mr Ding"
输出:"rM gniD"

提示:

  • 1 <= s.length <= 5 * 10的4次方
  • s 包含可打印的 ASCII 字符。
  • s 不包含任何开头或结尾空格。
  • s至少 有一个词。
  • s 中的所有单词都用一个空格隔开
cpp 复制代码
#include <cstdio>
#include <cstring>
#include <cstdlib>

// 函数:反转字符串中从start位置开始的sSize个字符
void reverseString(char* s, int start, int sSize) {
    int half = sSize / 2; // 计算需要交换的次数(一半的长度)
    for (int i = 0; i < half; i++) {
        char temp = s[start + i]; // 临时存储当前字符
        s[start + i] = s[start + sSize - i - 1]; // 将当前字符替换为对应位置的字符
        s[start + sSize - i - 1] = temp; // 将对应位置的字符替换为临时存储的字符
    }
}

// 函数:反转字符串中的每个单词
char* reverseWords(char* s) {
    int len = strlen(s); // 获取字符串长度
    printf("Original length: %d\n", len);
    int start = 0; // 当前单词的起始位置
    int length = 0; // 当前单词的长度
    for (int i = 0; i < len; ++i) {
        if (s[i] != ' ') { // 如果当前字符不是空格
            length++; // 增加当前单词的长度
        } else { // 如果遇到空格,则当前单词结束
        
            reverseString(s, start, length); // 反转当前单词
            start = i + 1; // 更新下一个单词的起始位置
            length = 0; // 重置当前单词的长度
        }
    }
    // 处理最后一个单词(可能后面没有空格)
    reverseString(s, start, length);
    // puts(s); // 打印反转后的字符串(调试用)
    return s; // 返回反转后的字符串
}

int main() {
    char s[] = "Let's take LeetCode contest"; // 定义一个字符数组
    char* ans = reverseWords(s); // 调用reverseWords函数反转每个单词
    for (int i = 0; i < strlen(ans); ++i) {
        printf("%c", ans[i]); // 打印处理后的字符串
    }
    return 0;
}

58. 最后一个单词的长度

给你一个字符串 s,由若干单词组成,单词前后用一些空格字符隔开。返回字符串中 最后一个 单词的长度。

单词 是指仅由字母组成、不包含任何空格字符的最大子字符串

示例 1:

复制代码
输入:s = "Hello World"
输出:5
解释:最后一个单词是“World”,长度为 5。

示例 2:

复制代码
输入:s = "   fly me   to   the moon  "
输出:4
解释:最后一个单词是“moon”,长度为 4。

示例 3:

复制代码
输入:s = "luffy is still joyboy"
输出:6
解释:最后一个单词是长度为 6 的“joyboy”。

提示:

  • 1 <= s.length <= 10的4次方
  • s 仅有英文字母和空格 ' ' 组成
  • s 中至少存在一个单词
cpp 复制代码
#include <cstdio>
#include <cstring>

// 函数:反转字符串
void reverseString(char* str) {
    size_t length = strlen(str);  // 获取字符串长度
    size_t half = length / 2;     // 需要反转的前半部分长度

    // 交换前半部分和后半部分的字符
    for (int i = 0; i < half; i++) {
        char temp = str[i];               // 保存前半部分的字符
        str[i] = str[length - i - 1];     // 将后半部分的字符放到前半部分的位置
        str[length - i - 1] = temp;       // 将前半部分的字符放到后半部分的位置
    }
}

// 函数:计算字符串中最后一个单词的长度
int lengthOfLastWord(char* s) {
    reverseString(s);  // 先反转整个字符串,以便从开头开始找第一个单词

    size_t length = strlen(s);  // 获取反转后的字符串长度
    int i = 0;                  // 用于遍历字符串的指针
    int index = 0;              // 记录第一个非空格字符的索引

    // 找到第一个非空格字符的位置
    while (i < length) {
        if (s[i] != ' ') {  // 如果当前字符不是空格
            index = i;      // 记录当前位置为第一个单词的起始位置
            break;          // 跳出循环,开始统计单词长度
        }
        i++;                // 跳过空格
    }

    // 统计从起始位置到下一个空格的距离(即最后一个单词的长度)
    int size = 0;
    for (int j = index; j < length; ++j) {
        if (s[j] != ' ') {  // 如果当前字符不是空格
            size++;         // 增加单词长度
        } else {            // 如果遇到空格
            break;          // 结束统计
        }
    }

    return size;            // 返回最后一个单词的长度
}

int main() {
    // 定义测试字符串
    char s[] = "   fly me   to   the moon  ";

    // 计算最后一个单词的长度
    int lastWordLength = lengthOfLastWord(s);

    // 输出结果
    printf("The length of the last word is: %d\n", lastWordLength);

    return 0;
}

389. 找不同

给定两个字符串 st ,它们只包含小写字母。

字符串 t 由字符串 s 随机重排,然后在随机位置添加一个字母。

请找出在 t 中被添加的字母。

示例 1:

复制代码
输入:s = "abcd", t = "abcde"
输出:"e"
解释:'e' 是那个被添加的字母。

示例 2:

复制代码
输入:s = "", t = "y"
输出:"y"

提示:

  • 0 <= s.length <= 1000
  • t.length == s.length + 1
  • st 只包含小写字母
cpp 复制代码
#include <cstdio>
#include <cstring>
#include <cstdlib>

// 函数:找出字符串 t 中相对于字符串 s 多出的一个字符
char findTheDifference(char* s, char* t) {
    // 如果 s 为空,则 t 的第一个字符就是答案
    if (s == NULL) {
        return t[0];
    }

    // 计算字符串 t 和 s 的长度
    int t_len = strlen(t);
    int s_len = strlen(s);

    // 创建两个数组,用于统计 26 个字母在 t 和 s 中的出现次数
    int* ans1 = (int*) malloc(26 * sizeof(int)); // 用于 t
    int* ans2 = (int*) malloc(26 * sizeof(int)); // 用于 s

    // 初始化数组为 0
    memset(ans1, 0, 26 * sizeof(int));
    memset(ans2, 0, 26 * sizeof(int));

    // 统计 t 中每个字母的出现次数
    for (int i = 0; i < t_len; ++i) {
        int index = t[i] - 'a'; // 计算字母的索引(a=0, b=1, ..., z=25)
        ans1[index]++;
    }

    // 统计 s 中每个字母的出现次数
    for (int i = 0; i < s_len; ++i) {
        int index = s[i] - 'a'; // 计算字母的索引
        ans2[index]++;
    }

    // 比较两个数组,找出出现次数不同的字母
    char temp = '\0'; // 用于存储结果
    for (int i = 0; i < 26; ++i) {
        if (ans1[i] != ans2[i]) { // 如果某个字母的出现次数不同
            temp = i + 'a'; // 计算对应的字符
            break; // 找到后立即退出循环
        }
    }

    // 释放动态分配的内存
    free(ans1);
    free(ans2);
    free(s);
    free(t);

    return temp; // 返回多出的字符
}

int main() {
    char s[] = "abcd"; // 定义一个字符数组 s
    char t[] = "abcde"; // 定义一个字符数组 t
    char ans = findTheDifference(s, t); // 调用 findTheDifference 函数
    printf("%c ", ans); // 打印结果
    return 0;
}

383. 赎金信

给你两个字符串:ransomNotemagazine ,判断 ransomNote 能不能由 magazine 里面的字符构成。

如果可以,返回 true ;否则返回 false

magazine 中的每个字符只能在 ransomNote 中使用一次。

示例 1:

复制代码
输入:ransomNote = "a", magazine = "b"
输出:false

示例 2:

复制代码
输入:ransomNote = "aa", magazine = "ab"
输出:false

示例 3:

复制代码
输入:ransomNote = "aa", magazine = "aab"
输出:true

提示:

  • 1 <= ransomNote.length, magazine.length <= 105
  • ransomNotemagazine 由小写英文字母组成
cpp 复制代码
#include <cstdio>
#include <cstring>
#include <cstdlib>

// 函数:判断赎金信字符串能否用杂志中的字符构造
bool canConstruct(char* ransomNote, char* magazine) {

    // 获取杂志和赎金信的长度
    int magazine_len = strlen(magazine);
    int ransomNote_len = strlen(ransomNote);

    // 创建两个数组,用于统计每个字母在杂志和赎金信中的出现次数
    int* ans1 = (int*) malloc(26 * sizeof(int)); // 统计杂志中的字母次数
    memset(ans1, 0, 26 * sizeof(int));

    int* ans2 = (int*) malloc(26 * sizeof(int)); // 统计赎金信中的字母次数
    memset(ans2, 0, 26 * sizeof(int));

    // 统计杂志中每个字母的出现次数
    for (int i = 0; i < magazine_len; ++i) {
        int index = magazine[i] - 'a'; // 将字符转换为对应的索引(0-25)
        ans1[index]++; // 增加对应字符的计数
    }

    // 打印杂志的字母计数(调试用)
    printf("magazine\t:");
    for (int i = 0; i < 26; ++i) {
        printf("%d ", ans1[i]);
    }
    printf("\n");

    // 统计赎金信中每个字母的出现次数
    for (int i = 0; i < ransomNote_len; ++i) {
        int index = ransomNote[i] - 'a'; // 将字符转换为对应的索引
        ans2[index]++; // 增加对应字符的计数
    }

    // 打印赎金信的字母计数(调试用)
    printf("ransomNote\t:");
    for (int i = 0; i < 26; ++i) {
        printf("%d ", ans2[i]);
    }
    printf("\n");

    // 找出杂志和赎金信中的最大字母索引值
    int max1 = magazine[0] - 'a';
    int max2 = ransomNote[0] - 'a';

    // 更新杂志的最大字母索引值
    for (int i = 0; i < magazine_len; ++i) {
        int temp = magazine[i] - 'a';
        if (temp > max1) {
            max1 = temp;
        }
    }

    // 更新赎金信的最大字母索引值
    for (int i = 0; i < ransomNote_len; ++i) {
        int temp = ransomNote[i] - 'a';
        if (temp > max2) {
            max2 = temp;
        }
    }

    // 打印最大字母索引值
    printf("max1:%d   max2:%d\n", max1, max2);

    // 取最大的字母索引值
    int max = max1 > max2 ? max1 : max2;
    int flag = 0; // 用于记录满足条件的字母数量

    // 遍历所有可能用到的字母索引
    for (int i = 0; i <= max; ++i) {
        if (ans1[i] >= ans2[i]) { // 如果杂志中的字母计数 >= 赎金信中的
            flag++; // 增加满足条件的字母数量
        }
    }

    // 打印调试信息
    printf("\nmax:%d ,", max);
    printf("\nflag:%d", flag);
    printf("\nransomNote_len:%d", ransomNote_len);

    // 如果所有需要的字母数量都在杂志中足够,则返回 true
    if (flag == max + 1) {
        return true;
    }

    // 释放动态分配的内存
    free(ans1);
    free(ans2);

    return false;
}

int main() {
    // 定义测试字符串
    char ransomNote[] = "fihjjjjei";
    char magazine[] = "hjibagacbhadfaefdjaeaebgi";

    // 调用 canConstruct 函数并打印结果
    bool ans = canConstruct(ransomNote, magazine);
    if (ans) {
        printf("true");
    } else {
        printf("false");
    }

    return 0;
}

大佬写的

cpp 复制代码
#include <stdio.h>
#include <string.h>

bool canConstruct(char* ransomNote, char* magazine) {
    int i = 0; // 初始化遍历指针,用于遍历 ransomNote 字符串

    // 遍历 ransomNote 的每个字符,直到遇到字符串结束符 '\0'
    while (ransomNote[i] != '\0') {
        // 使用 strchr 函数在 magazine 中查找当前字符
        char* found = strchr(magazine, ransomNote[i]);

        // 如果找不到当前字符,返回 false
        if (found == NULL) {
            return false;
        } else {
            // 计算找到的字符在 magazine 中的索引位置
            int index = found - magazine;

            // 将找到的字符标记为 '#',避免重复使用
            magazine[index] = '#';

            // 继续检查 ransomNote 的下一个字符
            i++;
        }
    }

    // 如果所有字符都能找到并且标记,则返回 true
    return true;
}

int main() {
    char ransomNote[] = "aa";
    char magazine[] = "aab";

    // 调用 canConstruct 函数
    if (canConstruct(ransomNote, magazine)) {
        printf("true\n");
    } else {
        printf("false\n");
    }

    return 0;
}

242. 有效的字母异位词

给定两个字符串 st ,编写一个函数来判断 t 是否是 s

字母异位词。

示例 1:

复制代码
输入: s = "anagram", t = "nagaram"
输出: true

示例 2:

复制代码
输入: s = "rat", t = "car"
输出: false

提示:

  • 1 <= s.length, t.length <= 5 * 104
  • st 仅包含小写字母
cpp 复制代码
#include <cstdio>  // 用于标准输入输出
#include <cstring> // 用于字符串处理函数,如strlen和strchr

// 函数:判断两个字符串是否是字母异位词
// 参数:s 和 t 是两个字符指针,指向要比较的字符串
// 返回值:如果s和t是字母异位词,返回true;否则返回false
bool isAnagram(char* s, char* t) {
    // 首先检查两个字符串的长度是否相等
    // 如果长度不同,直接返回false
    if (strlen(s) != strlen(t)) {
        return false;
    }
    
    // 初始化索引i,用于遍历字符串t
    int i = 0;
    while (t[i] != '\0') {
        // 在字符串s中查找字符t[i]
        char* found = strchr(s, t[i]);
        
        // 如果没有找到字符t[i],说明s和t不是字母异位词
        if (found == NULL) {
            return false;
        } else {
            // 计算找到的字符在s中的索引
            int index = found - s;
            // 将该字符标记为已使用,避免重复计数
            s[index] = '#';
            // 继续处理下一个字符
            i++;
        }
    }
    // 所有字符都成功匹配,返回true
    return true;
}

int main() {
    // 定义两个字符串s和t
    char s[] = "ab";
    char t[] = "a";
    
    // 调用isAnagram函数检查是否是字母异位词
    if (isAnagram(s, t)) {
        printf("true\n");
    } else {
        printf("false\n");
    }
    
    return 0;
}

2094. 找出 3 位偶数

给你一个整数数组 digits ,其中每个元素是一个数字(0 - 9)。数组中可能存在重复元素。

你需要找出 所有 满足下述条件且 互不相同 的整数:

  • 该整数由 digits 中的三个元素按 任意 顺序 依次连接 组成。
  • 该整数不含 前导零
  • 该整数是一个 偶数

例如,给定的 digits[1, 2, 3] ,整数 132312 满足上面列出的全部条件。

将找出的所有互不相同的整数按 递增顺序 排列,并以数组形式返回*。*

示例 1:

复制代码
输入:digits = [2,1,3,0]
输出:[102,120,130,132,210,230,302,310,312,320]
解释:
所有满足题目条件的整数都在输出数组中列出。 
注意,答案数组中不含有 奇数 或带 前导零 的整数。

示例 2:

复制代码
输入:digits = [2,2,8,8,2]
输出:[222,228,282,288,822,828,882]
解释:
同样的数字(0 - 9)在构造整数时可以重复多次,重复次数最多与其在 digits中出现的次数一样。 
在这个例子中,数字 8 在构造 288、828 和 882 时都重复了两次。 

示例 3:

复制代码
输入:digits = [3,7,5]
输出:[]
解释:使用给定的 digits 无法构造偶数。

提示:

  • 3 <= digits.length <= 100
  • 0 <= digits[i] <= 9
cpp 复制代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

// 统计数组元素的频率
int* getPL(int* digits, int digitsSize) {
    // 使用 calloc 初始化内存为零
    int* pl = (int*)calloc(10, sizeof(int));
    if (pl == NULL) {
        printf("Memory allocation failed\n");
        exit(1);
    }
    for (int i = 0; i < digitsSize; ++i) {
        int num = digits[i];
        if (num < 0 || num > 9) {
            printf("Invalid digit: %d\n", num);
            continue;
        }
        pl[num]++;
    }
    return pl;
}

// 检查三位数 num 是否可能
bool isPossible(int num, int* pl) {
    int digits[3];
    digits[0] = num / 100;
    digits[1] = (num / 10) % 10;
    digits[2] = num % 10;

    int tempPL[10];
    memcpy(tempPL, pl, sizeof(tempPL));
    for (int i = 0; i < 3; ++i) {
        if (tempPL[digits[i]] == 0) {
            return false;
        }
        tempPL[digits[i]]--;
    }
    return true;
}

// 添加数字到结果数组
void addNumber(int num, int* pl, int* result, int* count) {
    if (isPossible(num, pl)) {
        result[(*count)++] = num;
    }
}

// 比较函数用于 qsort
int compare(const void* a, const void* b) {
    return (*(int*)a - *(int*)b);
}

// 找出全部的偶数,按照递增的顺序排列
int* findEvenNumbers(int* digits, int digitsSize, int* returnSize) {
    // 获取频率
    int* pl = getPL(digits, digitsSize);
    for (int i = 0; i < 10; ++i) {
        printf("%d ", pl[i]);
    }
    printf("\n");

    // 生成全部的三位数
    // 结果数组,最大1000个数
    int* result = (int*)calloc(1000, sizeof(int));
    if (result == NULL) {
        printf("Memory allocation failed\n");
        exit(1);
    }
    int count = 0;  // count 是添加元素的位置

    for (int i = 100; i < 1000; i += 2) {  // 生成三位偶数
        addNumber(i, pl, result, &count);
    }
    qsort(result, count, sizeof(int), compare);

    *returnSize = count;
    return result;
}

int main() {
    int digits[] = {2, 1, 3, 0};
    int digitsSize = sizeof(digits) / sizeof(digits[0]);
    int returnSize = 0;
    int* ans = findEvenNumbers(digits, digitsSize, &returnSize);
    for (int i = 0; i < returnSize; ++i) {
        printf("%d ", ans[i]);
    }
    printf("\n");
    free(ans);  // 释放分配的内存
    return 0;
}

大佬写的,还得是大佬

cpp 复制代码
#include <stdio.h>
#include <stdlib.h>

// 函数:查找并返回由给定数字组成的三位偶数(按递增顺序排列)
// 参数:
// - digits: 输入数组,包含可用的数字
// - digitsSize: 输入数组的大小
// - returnSize: 输出结果数组的大小
int* findEvenNumbers(int* digits, int digitsSize, int* returnSize) {
    // 统计每个数字(0-9)的出现频率
    int num[10] = {0}; // 用于记录0-9数字的出现次数
    for (int i = 0; i < digitsSize; i++) {
        num[digits[i]]++; // 统计每个数字出现的次数
    }

    // 分配内存以存储结果数组,最多可能有1000个三位数
    int *ans = (int *)malloc(sizeof(int) * 1000); // 动态分配内存,大小为1000个整数
    int idx = 0; // 用于记录当前结果数组中添加的元素数目

    // 遍历所有可能的三位偶数,三位数的范围是100-999,步长为2(确保个位是偶数)
    for (int i = 100; i <= 998; i += 2) { // 为什么是998?因为步长为2,最后一位是998
        // 创建一个数组,用于临时统计当前三位数各个数字的出现次数
        int cnt[10] = {0};
        int tmp = i; // 复制当前三位数,用于分解

        // 分解三位数,检查是否可以由输入数组中的数字组成
        while (tmp > 0) {
            int c = tmp % 10; // 获取当前数字的最后一位
            cnt[c]++; // 增加该数字的出现次数
            // 如果当前数字的出现次数超过输入数组中该数字的可用次数,说明无法组成
            if (cnt[c] > num[c]) {
                break; // 跳出循环,停止分解
            }
            tmp /= 10; // 去除最后一位
        }

        // 如果分解后的tmp等于0,说明分解成功,所有数字的出现次数都未超过输入数组的可用次数
        if (tmp == 0) {
            ans[idx++] = i; // 将该三位数添加到结果数组中
        }
    }

    // 返回结果数组的大小
    *returnSize = idx;
    return ans; // 返回结果数组
}

// 示例测试函数
int main() {
    int digits[] = {2, 1, 3, 0}; // 输入数字数组
    int digitsSize = sizeof(digits) / sizeof(digits[0]); // 数组大小
    int returnSize;
    int *ans = findEvenNumbers(digits, digitsSize, &returnSize); // 调用函数

    // 打印结果
    for (int i = 0; i < returnSize; i++) {
        printf("%d ", ans[i]);
    }
    printf("\n");

    // 释放分配的内存(可选,取决于使用场景)
    free(ans);

    return 0;
}

824. 山羊拉丁文

给你一个由若干单词组成的句子 sentence ,单词间由空格分隔。每个单词仅由大写和小写英文字母组成。

请你将句子转换为 " 山羊拉丁文(Goat Latin"(一种类似于 猪拉丁文 - Pig Latin 的虚构语言)。山羊拉丁文的规则如下:

  • 如果单词以元音开头('a', 'e', 'i', 'o', 'u'),在单词后添加"ma"
    • 例如,单词 "apple" 变为 "applema"
  • 如果单词以辅音字母开头(即,非元音字母),移除第一个字符并将它放到末尾,之后再添加"ma"
    • 例如,单词 "goat" 变为 "oatgma"
  • 根据单词在句子中的索引,在单词最后添加与索引相同数量的字母'a',索引从 1 开始。
    • 例如,在第一个单词后添加 "a" ,在第二个单词后添加 "aa" ,以此类推。

返回将 sentence 转换为山羊拉丁文后的句子。

示例 1:

复制代码
输入:sentence = "I speak Goat Latin"
输出:"Imaa peaksmaaa oatGmaaaa atinLmaaaaa"

示例 2:

复制代码
输入:sentence = "The quick brown fox jumped over the lazy dog"
输出:"heTmaa uickqmaaa rownbmaaaa oxfmaaaaa umpedjmaaaaaa overmaaaaaaa hetmaaaaaaaa azylmaaaaaaaaa ogdmaaaaaaaaaa"

提示:

  • 1 <= sentence.length <= 150
  • sentence 由英文字母和空格组成
  • sentence 不含前导或尾随空格
  • sentence 中的所有单词由单个空格分隔
cpp 复制代码
#include <cstdio>    // 包含输入输出流头文件
#include <cstdlib>   // 包含标准库函数头文件
#include <cstring>   // 包含字符串处理函数头文件
#include <cctype>    // 包含字符处理函数头文件

/**
 * isVowel函数判断一个字符是否是元音字母。
 *
 * @param c 要判断的字符。
 * @return 返回true如果字符是元音字母,否则返回false。
 */
bool isVowel(char c) {
    char lower = tolower(c); // 将字符转换为小写
    return lower == 'a' || lower == 'e' || lower == 'i' || lower == 'o' || lower == 'u'; // 检查是否是元音字母
}

/**
 * process函数将单个单词转换为Goat Latin。
 *
 * @param word 输入的单词。
 * @param result 用于存储转换后结果的字符串。
 * @param pos 指向结果字符串的当前位置。
 * @param no 当前单词的序号。
 */
void process(char* word, char* result, int* pos, int no) {
    int len = strlen(word); // 获取单词长度
    int j = *pos; // 获取当前结果字符串的写入位置

    // 检查单词是否以元音字母开头
    if (isVowel(word[0])) {
        strcpy(&result[j], word); // 如果是元音开头,直接拷贝单词到结果
        j += len; // 更新结果位置
    } else {
        // 如果单词以辅音开头,将首字母移到末尾
        for (int i = 1; i < len; i++) { // 从第二个字符开始拷贝
            result[j++] = word[i];
        }
        result[j++] = word[0]; // 将首字母添加到末尾
    }

    // 添加"ma"和相应数量的"a"
    strcpy(&result[j], "ma"); // 添加"ma"
    j += 2; // 更新位置
    for (int i = 0; i < no; i++) { // 添加no个"a"
        result[j++] = 'a';
    }

    // 添加空格分隔单词
    result[j++] = ' ';
    *pos = j; // 更新结果字符串的当前写入位置
}


char* toGoatLatin(char* sentence) {
    int len = strlen(sentence); // 获取句子长度

    // 为结果字符串分配内存
    char* result = (char *)(calloc(150, sizeof(char))); // 150是预估的足够大的空间
    if (!result) {
        return NULL; // 内存分配失败
    }

    // 分配临时字符串存储每个单词
    char* temp = (char *)(calloc(150, sizeof(char)));
    if (!temp) {
        free(result); // 释放结果字符串的内存
        return NULL; // 内存分配失败
    }

    int pos = 0; // 当前结果字符串的写入位置
    int no = 0;  // 当前单词的序号

    // 使用strtok拆分句子为单词
    char* token = strtok(sentence, " ");
    while (token != NULL) {
        no++; // 增加单词序号
        strcpy(temp, token); // 将当前单词拷贝到临时字符串
        process(temp, result, &pos, no); // 处理单词并写入结果
        token = strtok(NULL, " "); // 获取下一个单词
    }

    // 移除末尾的空格
    if (pos > 0) {
        result[pos - 1] = '\0'; // 用'\0'替换最后一个空格
    }

    free(temp); // 释放临时字符串的内存
    return result; // 返回结果
}

int main() {
    char sentence[] = "I speak Goat Latin"; // 定义输入句子
    char* ans = toGoatLatin(sentence);
    puts(ans);                              // 输出结果
    return 0;
}

strtok 函数是 C 标准库中的一个字符串处理函数,主要用于将字符串分割成一系列的子字符串,这些子字符串由指定的分隔符来划分。它的全名是 "string token",即字符串标记。

cpp 复制代码
char *strtok(char *str, const char *delim);
  • str:指向要分割的字符串的指针。在第一次调用时,str 应该指向待分割的字符串。在后续的调用中,str 应该是 NULL,这样 strtok 会继续从上一次分割的位置开始处理。

  • delim:指向分隔符字符串的指针,其中包含了用于分割字符串的所有字符。

功能

strtok 函数会修改原始字符串,在每个分隔符的位置插入空字符 \0,从而将字符串分割成多个子字符串。每次调用 strtok 都会返回下一个标记(token)的指针,直到没有更多的标记为止,此时返回 NULL

cpp 复制代码
#include <stdio.h>
#include <string.h>

int main() {
    char str[] = "Hello,world,this,is,a,test";
    char *token;

    token = strtok(str, ",");
    while (token != NULL) {
        printf("%s\n", token);
        token = strtok(NULL, ",");
    }

    return 0;
}

工作原理

  1. 第一次调用

    • strtok 接受字符串和分隔符作为参数。

    • 它会在字符串中查找第一个分隔符,并在其位置插入 \0,从而分割出第一个标记。

    • 返回第一个标记的指针。

  2. 后续调用

    • str 参数设置为 NULL

    • strtok 会继续从上次分割的位置开始查找下一个分隔符,并进行类似的处理。

    • 重复这个过程,直到没有更多的标记。

83. 删除排序链表中的重复元素

给定一个已排序的链表的头 head删除所有重复的元素,使每个元素只出现一次 。返回 已排序的链表

示例 1:

复制代码
输入:head = [1,1,2]
输出:[1,2]

示例 2:

复制代码
输入:head = [1,1,2,3,3]
输出:[1,2,3]

提示:

  • 链表中节点数目在范围 [0, 300]
  • -100 <= Node.val <= 100
  • 题目数据保证链表已经按升序 排列
cpp 复制代码
#include <cstdio>    // 包含输入输出流头文件
#include <cstdlib>   // 包含标准库函数头文件
#include <cstring>   // 包含字符串处理函数头文件
#include <cctype>    // 包含字符处理函数头文件

// 定义链表节点结构体
struct ListNode {
    int val;             // 节点值
    struct ListNode *next; // 指向下一个节点的指针
};

// 初始化链表函数
void init(ListNode** L) {
    *L = NULL; // 将链表初始化为空(头指针置为NULL)
}

// 创建链表函数(尾插法)
void create(ListNode** L, int num) {
    struct ListNode* temp = NULL; // 临时指针,用于创建新节点
    struct ListNode* tail = NULL; // 尾指针,用于跟踪链表尾部
    int i = 0, e;                // 循环计数器和输入值

    // 使用尾插法创建链表
    while (i < num) {
        // 动态分配新节点
        temp = (struct ListNode*)malloc(sizeof(struct ListNode));
        if (temp == NULL) {
            printf("内存分配失败!\n");
            exit(1); // 内存分配失败时退出程序
        }

        // 提示用户输入第i+1个元素的值
        printf("input the No.%d: ", i + 1);
        scanf("%d", &e); // 读取用户输入

        // 初始化新节点
        temp->val = e;   // 设置节点值
        temp->next = NULL; // 新节点的next指针初始化为NULL

        // 如果链表为空(头指针为NULL),则将新节点作为头节点
        if (*L == NULL) {
            *L = temp; // 头指针指向新节点
            tail = temp; // 尾指针也指向新节点
        } else {
            tail->next = temp; // 将新节点链接到链表末尾
            tail = temp; // 更新尾指针
        }

        i++; // 循环计数器加1
    }
}

// 删除链表中重复节点函数
struct ListNode* deleteDuplicates(struct ListNode* head) {
    if (head == NULL) {
        return head; // 如果链表为空,直接返回
    }

    struct ListNode* p = head; // 指向当前节点的指针
    struct ListNode* q = p->next; // 指向当前节点的下一个节点的指针
    struct ListNode* temp; // 临时指针,用于保存被删除的节点

    // 遍历链表
    while (q) {
        if (q->val == p->val) {
            // 如果当前节点值等于前一节点值,则删除当前节点
            temp = q; // 保存当前节点
            p->next = q->next; // 前一节点的next指向当前节点的下一个节点
            q = p->next; // 当前节点移动到下一个节点
            free(temp); // 释放被删除节点的内存
        } else {
            // 如果当前节点值不等于前一节点值,继续遍历
            p = p->next; // 前一节点移动到当前节点
            q = p->next; // 当前节点移动到下一个节点
        }
    }

    return head; // 返回处理后的链表头节点
}

int main() {
    struct ListNode* L; // 定义链表头指针
    init(&L); // 初始化链表

    int num = 0;
    printf("how many do you want to create?"); // 提示用户输入链表元素数量
    scanf("%d", &num); // 读取用户输入的链表元素数量
    create(&L, num); // 创建链表

    // 删除链表中的重复节点
    struct ListNode* head = deleteDuplicates(L);
    struct ListNode* p = head; // 定义指针用于遍历链表

    // 输出处理后的链表
    while (p) {
        printf("%d ", p->val); // 打印节点值
        p = p->next; // 移动到下一个节点
    }

   
    return 0;
}

657. 机器人能否返回原点

在二维平面上,有一个机器人从原点 (0, 0) 开始。给出它的移动顺序,判断这个机器人在完成移动后是否在**(0, 0) 处结束**。

移动顺序由字符串 moves 表示。字符 move[i] 表示其第 i 次移动。机器人的有效动作有 R(右),L(左),U(上)和 D(下)。

如果机器人在完成所有动作后返回原点,则返回 true。否则,返回 false

注意: 机器人"面朝"的方向无关紧要。 "R" 将始终使机器人向右移动一次,"L" 将始终向左移动等。此外,假设每次移动机器人的移动幅度相同。

示例 1:

复制代码
输入: moves = "UD"
输出: true
解释:机器人向上移动一次,然后向下移动一次。所有动作都具有相同的幅度,因此它最终回到它开始的原点。因此,我们返回 true。

示例 2:

复制代码
输入: moves = "LL"
输出: false
解释:机器人向左移动两次。它最终位于原点的左侧,距原点有两次 “移动” 的距离。我们返回 false,因为它在移动结束时没有返回原点。

提示:

  • 1 <= moves.length <= 2 * 104
  • moves 只包含字符 'U', 'D', 'L''R'
cpp 复制代码
#include <cstdio>    // 包含输入输出流头文件


bool judgeCircle(char* moves) {
    int x = 0,y = 0;
    int i = 0;
    while (moves[i] != '\0'){
        switch (moves[i]) {
            case 'U':
                y++;
                break;
            case 'D':    
                y--;
                break;
            case 'R':
                x++;
                break;
            case 'L':
                x--;
                break;
        }
        i++;
    }
    return x==0 && y==0;
}

int main() {
    char* s = "UD";
    if (judgeCircle(s)){
        puts("true");
    } else{
        puts("false");
    }

    return 0;
}

551. 学生出勤记录 I

给你一个字符串 s 表示一个学生的出勤记录,其中的每个字符用来标记当天的出勤情况(缺勤、迟到、到场)。记录中只含下面三种字符:

  • 'A':Absent,缺勤
  • 'L':Late,迟到
  • 'P':Present,到场

如果学生能够 同时 满足下面两个条件,则可以获得出勤奖励:

  • 总出勤 计,学生缺勤('A'严格 少于两天。
  • 学生 不会 存在 连续 3 天或 连续 3 天以上的迟到('L')记录。

如果学生可以获得出勤奖励,返回 true ;否则,返回 false

示例 1:

复制代码
输入:s = "PPALLP"
输出:true
解释:学生缺勤次数少于 2 次,且不存在 3 天或以上的连续迟到记录。

示例 2:

复制代码
输入:s = "PPALLL"
输出:false
解释:学生最后三天连续迟到,所以不满足出勤奖励的条件。

提示:

  • 1 <= s.length <= 1000
  • s[i]'A''L''P'
cpp 复制代码
#include <cstdio>    // 包含输入输出流头文件

/**
 * checkRecord函数检查一个员工的考勤记录是否满足以下条件:
 * 1. 缺席('A')不超过一次。
 * 2. 连续迟到('L')不超过两次。
 *
 * @param s 考勤记录字符串。
 * @return 如果满足条件返回true,否则返回false。
 */
bool checkRecord(const char* s) {
    int a = 0; // 统计缺席('A')的次数
    int l = 0; // 统计连续迟到('L')的次数
    int i = 0; // 字符串索引

    while (s[i] != '\0') { // 遍历字符串直到结束符
        if (s[i] == 'A') {
            a++; // 遇到'P'时,缺席次数加1
            l = 0; // 超过三个连续L或者A出现的时候,将连续L数目置0
        } else if (s[i] == 'P') {
            l = 0; // 遇到'P'时,重置连续迟到次数
        } else if (s[i] == 'L') {
            l++; // 遇到'L'时,连续迟到次数加1
            if (l >= 3) {
                return false; // 如果连续迟到次数达到3次,则立即返回false
            }
        }
        i++;
    }

    return a < 2; // 最终检查缺席次数是否小于2次
}

int main() {
    char* s = "PPALLL"; // 定义一个考勤记录字符串

    if (checkRecord(s)) {
        puts("true"); // 如果满足条件,输出true
    } else {
        puts("false"); // 否则输出false
    }

    return 0;
}

482. 密钥格式化

给定一个许可密钥字符串 s,仅由字母、数字字符和破折号组成。字符串由 n 个破折号分成 n + 1 组。你也会得到一个整数 k

我们想要重新格式化字符串 s,使每一组包含 k 个字符,除了第一组,它可以比 k 短,但仍然必须包含至少一个字符。此外,两组之间必须插入破折号,并且应该将所有小写字母转换为大写字母。

返回 重新格式化的许可密钥

示例 1:

复制代码
输入:S = "5F3Z-2e-9-w", k = 4
输出:"5F3Z-2E9W"
解释:字符串 S 被分成了两个部分,每部分 4 个字符;
     注意,两个额外的破折号需要删掉。

示例 2:

复制代码
输入:S = "2-5g-3-J", k = 2
输出:"2-5G-3J"
解释:字符串 S 被分成了 3 个部分,按照前面的规则描述,第一部分的字符可以少于给定的数量,其余部分皆为 2 个字符。

提示:

  • 1 <= s.length <= 10的5次方
  • s 只包含字母、数字和破折号 '-'.
  • 1 <= k <= 10的4次方
cpp 复制代码
#include <stdio.h>    // 包含输入输出流头文件
#include <stdlib.h>   // 包含标准库函数头文件
#include <string.h>   // 包含字符串处理函数头文件
#include <ctype.h>    // 包含字符处理函数头文件

char* licenseKeyFormatting(char* s, int k) {
    // 计算有效字符(非'-')的数量
    int len = 0;
    for (int i = 0; s[i] != '\0'; i++) {
        if (s[i] != '-') len++;
    }

    // 如果没有有效字符,直接返回空字符串
    if (len == 0) {
        char* result = (char*)calloc(1, sizeof(char));
        return result;
    }

    // 计算第一组的长度(可能不足k个字符)
    int firstGroup = len % k;
    if (firstGroup == 0) firstGroup = k; // 如果整除,则第一组长度取k

    // 计算总长度:有效字符数 + 破折号的数量
    int totalLen = len + (len / k);
    if (firstGroup != k) totalLen++; // 如果第一组不是完整的k个,增加一个破折号

    // 分配结果字符串的内存(加1是为了存储结束符)
    char* result = (char*)calloc(totalLen + 1, sizeof(char));

    // 用于跟踪结果字符串的当前写入位置
    int indexOfResult = 0;

    // 移除破折号并将字符转换为大写
    char* temp = (char*)calloc(len + 1, sizeof(char));
    int tempIndex = 0;
    for (int i = 0; s[i] != '\0'; i++) {
        if (s[i] != '-') {
            temp[tempIndex++] = toupper(s[i]); // 转换为大写
        }
    }
    temp[tempIndex] = '\0'; // 结束字符串

    // 处理第一组字符
    if (firstGroup != 0) {
        // 拷贝第一组字符到结果
        for (int i = 0; i < firstGroup; i++) {
            result[indexOfResult++] = temp[i];
        }
        if (len - firstGroup > 0) {
            result[indexOfResult++] = '-'; // 添加破折号
        }
    }

    // 处理剩余的组
    for (int i = firstGroup; i < len; i += k) {
        if (i > firstGroup) { // 除了第一组外的其他组前加破折号
            result[indexOfResult++] = '-';
        }
        // 拷贝k个字符到结果(不足k个时处理到字符串末尾)
        for (int j = 0; j < k && i + j < len; j++) {
            result[indexOfResult++] = temp[i + j];
        }
    }

    result[indexOfResult] = '\0'; // 结束结果字符串
    free(temp); // 释放临时字符串的内存
    return result;
}

int main() {
    char s[] = "---"; // 定义输入字符串
    char* ans = licenseKeyFormatting(s, 3); // 调用格式化函数
    puts(ans); // 输出结果
    // 注意:需要手动释放ans的内存,以避免内存泄漏
    free(ans);
    return 0;
}

28. 找出字符串中第一个匹配项的下标

给你两个字符串 haystackneedle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1

示例 1:

复制代码
输入:haystack = "sadbutsad", needle = "sad"
输出:0
解释:"sad" 在下标 0 和 6 处匹配。
第一个匹配项的下标是 0 ,所以返回 0 。

示例 2:

复制代码
输入:haystack = "leetcode", needle = "leeto"
输出:-1
解释:"leeto" 没有在 "leetcode" 中出现,所以返回 -1 。

提示:

  • 1 <= haystack.length, needle.length <= 10的4次方
  • haystackneedle 仅由小写英文字符组成
cpp 复制代码
#include <cstdio>  // 引入标准输入输出库
#include <cstring> // 引入字符串操作库,用于 strlen 等函数
#include <cstdlib> // 引入<stdlib.h>,用于 calloc 等内存分配函数

// 函数:计算模式串的前缀函数(Longest Prefix Suffix - LPS 数组)
void computeLPSArray(char* pattern, int m, int* lsp) {
    int length = 0; // length 表示当前最长前缀后缀的长度,初始化为 0
    lsp[0] = 0;     // LPS 数组的第一个元素始终为 0,因为没有前缀和后缀

    int i = 1; // 从模式串的第二个字符开始处理
    while (i < m) {
        if (pattern[i] == pattern[length]) {
            // 如果当前字符与前缀字符匹配,则延长前缀长度
            length++;
            lsp[i] = length; // 更新 LPS 数组对应位置的值为当前 length
            i++;             // 移动到下一个字符
        } else {
            // 如果不匹配,根据前缀函数回退 length
            if (length != 0) {
                // 若 length 不为 0,回退到前一个匹配位置
                length = lsp[length - 1];
            } else {
                // 如果 length 为 0,说明无法匹配更长的前缀,将 LPS 数组对应位置设为 0
                lsp[i] = 0;
                i++; // 移动到下一个字符
            }
        }
    }
}

// 函数:实现基于 KMP 算法的字符串匹配
int strStr(char* haystack, char* needle) {
    int n = strlen(haystack); // 获取主串的长度
    int m = strlen(needle);   // 获取模式串的长度

    // 如果模式串长度大于主串长度,直接返回 -1
    if (n < m) {
        return -1;
    }

    // 为模式串的 LPS 数组分配足够的内存
    int* lsp = (int*)calloc(m, sizeof(int));
    computeLPSArray(needle, m, lsp); // 计算模式串的 LPS 数组

    int i = 0, j = 0; // i 指向主串的当前位置,j 指向模式串的当前位置
    while (i < n) {   // 遍历主串
        if (haystack[i] == needle[j]) {
            // 如果当前字符匹配,同时移动主串和模式串的指针
            j++;
            i++;
        }
        if (j == m) {
            // 当 j 达到模式串长度时,说明找到匹配子串
            free(lsp); // 释放 LPS 数组的内存
            return i - m; // 返回匹配子串的起始位置
        } else if (i < n && haystack[i] != needle[j]) {
            // 如果字符不匹配,且主串指针未越界
            if (j != 0) {
                // 如果模式串指针 j 不为 0,根据 LPS 数组回退 j
                j = lsp[j - 1];
            } else {
                // 如果 j 为 0,无法回退,只能移动主串指针 i 以继续查找
                i++;
            }
        }
    }

    // 如果循环结束仍未找到匹配,释放 LPS 数组的内存并返回 -1
    free(lsp);
    return -1;
}

int main() {
    char haystack[] = "mississippi"; // 主字符串
    char needle[] = "issip";         // 模式串

    // 调用 strStr 函数查找模式串在主串中的位置,并输出结果
    printf("%d", strStr(haystack, needle)); // 输出结果为 4

    return 0;
}

KMP算法:最浅显易懂的 KMP 算法讲解_哔哩哔哩_bilibili

1、初始化LPS数组

  • 创建一个长度为模式串长度的数组lsp,用于存储每个位置的最长前缀后缀长度。
  • 初始化lsp[0] = 0,因为第一个字符没有前缀和后缀。

2、遍历模式串

  • 从模式串的第二个字符开始(i = 1),逐个字符检查。

3、处理每个字符

  • i=1 ,字符's':
    • 当前最长前缀长度length=0
    • 比较pattern[1]('s')与pattern[0]('i'),不匹配。
    • 因此,lsp[1]=0length保持0,i增加到2。
  • i=2 ,字符's':
    • length=0
    • 比较pattern[2]('s')与pattern[0]('i'),不匹配。
    • lsp[2]=0i增加到3。
  • i=3 ,字符'i':
    • length=0
    • 比较pattern[3]('i')与pattern[0]('i'),匹配。
    • length增加到1,lsp[3]=1i增加到4。
  • i=4 ,字符'p':
    • length=1
    • 比较pattern[4]('p')与pattern[1]('s'),不匹配。
    • 因为length不为0,将length更新为lsp[length-1],即lsp[0]=0
    • 现在length=0,继续比较pattern[4]('p')与pattern[0]('i'),不匹配。
    • lsp[4]=0i增加到5,结束循环。

还是暴力破解吧!

cpp 复制代码
#include <cstdio>  // 引入标准输入输出库
#include <cstring> // 引入字符串操作库,用于 strlen 等函数

// 函数:查找子串在主串中的位置(暴力匹配)
int strStr(char* haystack, char* needle) {
    int n = strlen(haystack); // 获取主串的长度
    int m = strlen(needle);   // 获取子串的长度

    // 遍历主串中所有可能的起始位置
    for (int i = 0; i + m <= n; i++) {
        bool flag = true; // 标记是否找到匹配的子串

        // 逐个字符比较当前起始位置的子串与 needle
        for (int j = 0; j < m; j++) {
            if (haystack[i + j] != needle[j]) {
                flag = false; // 如果发现不匹配的字符,标记为 false
                break;        // 退出当前子串的比较
            }
        }

        // 如果 flag 为 true,说明找到匹配的子串
        if (flag) {
            return i; // 返回子串的起始位置
        }
    }

    // 如果循环结束仍未找到匹配的子串,返回 -1
    return -1;
}

int main() {
    char haystack[] = "mississippi"; // 主字符串
    char needle[] = "issip";         // 子串

    // 调用 strStr 函数查找子串在主串中的位置,并输出结果
    printf("%d", strStr(haystack, needle)); // 输出结果为 4

    return 0;
}
相关推荐
我不是程序猿儿3 分钟前
【C】识别一份嵌入式工程文件
c语言·开发语言
Dizzy.51734 分钟前
数据结构(查找)
数据结构·学习·算法
软件开发技术局1 小时前
撕碎QT面具(8):对控件采用自动增加函数(转到槽)的方式,发现函数不能被调用的解决方案
开发语言·qt
周杰伦fans2 小时前
C#中修饰符
开发语言·c#
yngsqq3 小时前
c# —— StringBuilder 类
java·开发语言
赔罪3 小时前
Python 高级特性-切片
开发语言·python
分别努力读书3 小时前
acm培训 part 7
算法·图论
武乐乐~3 小时前
欢乐力扣:赎金信
算法·leetcode·职场和发展
'Debug4 小时前
算法从0到100之【专题一】- 双指针第一练(数组划分、数组分块)
算法
Fansv5874 小时前
深度学习-2.机械学习基础
人工智能·经验分享·python·深度学习·算法·机器学习