Day 9 实现strStr()和外观数列

又拖了两天,真是要批评一下自己,虽然这两道题有需要加以理解的地方,但是每次想写的时候总是被一种无形的力量阻挠一下,还是得坚定一下自己的信念,其实想明白也不需要非常多的时间,提高一下对自己的要求还是必要的。

实现strStr()

这是第28题,题目的意思是找出字符串中第一个匹配项的下标,就是说现在给定一个主字符串,再给定一个目标字符串,需要在主字符串中寻找目标字符串,并将主字符串中目标字符串的首个下标找出并返回。

遍历求解

在读完这个题目之后,我便有了遍历的想法,就是我从主字符串中的每一个元素位置开始与目标字符串进行比较,如果不匹配,就从主字符串的下一个元素位置进行匹配,按照这样的办法直到匹配得到目标字符串或是遍历完主字符串,就可以完成这道题目的要求。

int strStr(char* haystack, char* needle) {
    if(strlen(haystack) < strlen(needle)){
        return -1;
    }
    for (int i = 0; i < strlen(haystack) - strlen(needle) + 1; i++)
    {
        int flag = 1;
        int j = 0;
        while (j < strlen(needle))
        {
            if (haystack[i+j] != needle[j])
            {
                flag = 0;
                break;
            }
            j++;
        }
        if (flag)
        {
            return i;
        }

    }
    return -1;
}

KMP算法

但是在看完题解后,发现针对于这样的一类问题有一种算法叫做KMP算法,可以在更低的时间复杂度解决此类问题,于是就学习了一下KMP算法。KMP算法与上述算法的不同之处在于KMP算法在出现不匹配情况时,可以根据已经匹配到的位置,判断继续从目标字符串的哪一位开始继续匹配,也就是说整个匹配过程中,用于指示主字符串中遍历位置的指针没有回溯,不需要回到主字符串开始匹配位置的下一位元素重新匹配,只需要调整指示目标字符串中匹配位置的指针即可继续进行字符串匹配的工作。

构建匹配表

在KMP算法中是通过对主字符串构建部分匹配表,实现在匹配目标字符串时控制指示目标字符串匹配位置的指针。关于构建部分匹配表最重要的概念就是最长相同前后缀数,一开始读到的时候感觉不太好理解,反复读了十几分钟发现自己理解了下来,最长相同前后缀数就是指该字符串从前往后和从后往前最长有几位是一样的。只是在这里是对主字符串中的部分字符串进行最长相同前后缀数的构建。也就是说假设有6位的字符串,就分别对前1位元素构成的字符串,前2位元素构成的字符串,前3位构成的字符串,直到前6位构成的字符串(也就是整个字符串),构建6位的匹配表数组,为后续算法服务。

// 计算 LPS 数组的函数
void computeLPSArray(char* pat, int M, int* lps) {
    int len = 0; // 最长相同前后缀的长度
    lps[0] = 0; // LPS 的第一个值总是 0

    // 计算 lps[i] 的值
    for (int i = 1; i < M; i++) {
        if (pat[i] == pat[len]) {
            len++;
            lps[i] = len;
        } else {
            if (len != 0) {
                len = lps[len - 1];
                // 注意这里不需要递增 i,因为我们还没有完成当前的 lps[i] 计算
            } else {
                lps[i] = 0;
            }
        }
    }
}

字符串匹配搜索算法

在构建完匹配表后,就可以实现KMP的搜索算法,一开始也是像遍历求解一样,从第一位开始进行两个字符串的匹配,当遇到不匹配的情况时,只需要查找该不匹配位置的部分匹配表数值,就知道将指示目标字符串匹配位置的指针移动多少位继续开始匹配工作。也就是说匹配表中的数值就是目标字符串匹配位置指针需要移动的位数。

// KMP 搜索算法
void KMPSearch(char* pat, char* txt) {
    int M = strlen(pat);
    int N = strlen(txt);
    int* lps = (int*)malloc(M * sizeof(int)); // 分配内存

    computeLPSArray(pat, M, lps); // 计算 LPS 数组

    int i = 0; // txt 的索引
    int j = 0; // pat 的索引
    while (i < N) {
        if (pat[j] == txt[i]) {
            j++;
            i++;
        }

        if (j == M) {
            printf("Found pattern at index %d \n", i - j);
            j = lps[j - 1];
        }

        // 不匹配的情况
        else if (i < N && pat[j] != txt[i]) {
            if (j != 0)
                j = lps[j - 1];
            else
                i = i + 1;
        }
    }

    free(lps); // 释放内存
}

外观数列

这是第38题,就是实现一种叫外观数列的输出,这种数列从1开始,随后在该数列中都是采用奇数位数字描述上一个数列中有几个相同的元素,偶数位数字描述这个元素是几,比如说第二个数列就是11,表示上一个数列有1个1;第三个数列就是21,表示上一个数列有2个1;第四个数列就是1211,表示上一个数列有1个2和1个1;以此类推,可以看出最后输出的数列与上一个数列有关,而每一个数列之间的输出关系也都明白,将数字之间相互比较,如果与上一个数字相同,那么就让指示有多少个该元素的count变量++,如果不同就将count变量置为1,而用于比较的数字也改成该数字,继续进行比较,不过最后需要考虑一下最后一个元素的边界条件,即该元素是否被遍历到以及是否在答案数列中有所体现

char* countAndSay(int n) {
    if (n == 1) {
        char* res = (char*)malloc(2 * sizeof(char));
        res[0] = '1';
        res[1] = '\0';
        return res;
    }

    char* lastString = countAndSay(n - 1);
    int ls_len = strlen(lastString);
    int count = 0, lastNum = 0;
    int p = 0;
    char* res = (char*)malloc((2 * ls_len + 1) * sizeof(char));

    for (int i = 0; i < ls_len; i++) {
        if (lastString[i] - '0' == lastNum)
            count++;
        else {
            if (i > 0) {
                res[p++] = (char)('0' + count);
                res[p++] = (char)('0' + lastNum);
            }
            count = 1;
            lastNum = lastString[i] - '0';
        }
    }

    res[p++] = (char)('0' + count);
    res[p++] = (char)('0' + lastNum);
    res[p] = '\0';

    free(lastString);
    return res;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值