1. 题目描述

problem link

Write a function to find the longest common prefix string amongst an array of strings.

If there is no common prefix, return an empty string "".

Example 1:

Input: ["flower","flow","flight"]
Output: "fl"

Example 2:

Input: ["dog","racecar","car"]
Output: ""
Explanation: There is no common prefix among the input strings.

Note:

All given inputs are in lowercase letters a-z.

2. 解题

最长公共前缀,方法很多,我用的是“垂直扫描”。

即依次比较每个字符串的第1位、第2位 ... 直到出现某一位并不是所有字符串都相同,就找到了最长前缀,返回即可。具体写代码的时候,可以直接以strs[0]作为最终返回的结果,因为最长前缀最长也不可能超过strs[0]的长度,最后找到最长前缀后直接在对应位写上'\0'即可。

时间复杂度:O(S),S是所有字符串字符数的和。可知在最坏情况下,所有字符串都完全相同,则需要比较S次。

类似的还有很多方法,例如“水平扫描”,即前两个字符串先找出一个最长公共前缀,在拿这个最长公共前缀和第三个字符串比较,依此类推。时间复杂度O(S)。

也可以用分治的思想,所有字符串两两比较找出一组最长公共前缀,再从这一组字符串中再两两比较再找出下一组最长公共前缀,依此类推最终得到总的最长公共前缀。时间复杂度O(S)。可知最坏情况第一次要比较 $ \frac{S}{2} $ 次,第二次要比较 $ \frac{S}{4} $ 次,依此类推最终时间复杂度O(S)。

最后还可以用前缀树这个数据结构,有关前缀树可以看这个文章

可以稍微修改一下前缀树的数据结构,里面加上一个字段size表示这个节点下有多少个非空子节点,方便找公共前缀。然后在判断什么时候找到了公共前缀可以看下面这个图:

这个前缀树插入了"le" "lead" "leet"三个字符串,可知最长前缀为"le",而判断找到最长前缀的条件有两个,一是当前节点的isEnd值为1,说明不可能有更长的公共前缀了,二是这个节点下面有不止一个非空子节点。

3. 代码

垂直扫描

char* longestCommonPrefix(char ** strs, int strsSize) {
    if (strsSize == 0) {
        return "";
    }
    if (strsSize == 1) {
        return strs[0];
    }

    for (int i = 0; i < strlen(strs[0]); i++) {  // 遍历strs[0]第i个字符
        char c = strs[0][i];
        for (int j = 1; j < strsSize; j++) {  // 和第strs[j]个字符串的第i个字符比较
            if (strs[j][i] != c || strs[j][i] == '\0') {
                strs[0][i] = '\0';
                return strs[0];
            }
        }
    }

    return strs[0];
}

前缀树

#define R 26  // 26个小写字母

typedef struct prefixTreeNode {
    struct prefixTreeNode* links[R];  // 每个节点26个分叉
    int size;  // 当前节点有几个非空子节点
    int isEnd;  // 0 or 1
} Node;

void nodeInit(Node** pRoot) {
    *pRoot = (Node *)malloc(sizeof(Node));
    for (int i = 0; i < R; i++) {
        (*pRoot)->links[i] = NULL;
    }
    (*pRoot)->isEnd = 0;
    (*pRoot)->size = 0;
}

void prefixTreeInsert(Node* root, char* str) {
    Node* cur = root;

    while (*str) {  // 取出str中每个字符
        if (cur->links[*str - 'a'] == NULL) {  // 如果这个字符不存在
            nodeInit(&(cur->links[*str - 'a']));
            cur->size++;
        }
        cur = cur->links[*str - 'a'];  // cur前进到下一层    
        str++;
    }
    cur->isEnd = 1;
}

// 返回当前节点有几个非空子节点
int getLinks(Node* pNode) {
    return pNode->size;
}

char * longestCommonPrefix1(char ** strs, int strsSize) {
    if (strsSize == 0) {
        return "";
    }
    if (strsSize == 1) {
        return strs[0];
    }

    // 前缀树初始化
    Node* root;
    nodeInit(&root);

    // 将所有字符串插入前缀树
    for (int i = 0; i < strsSize; i++) {
        prefixTreeInsert(root, strs[i]);
    }
    
    // 寻找最长公共前缀
    char* res = (char *)malloc(sizeof(char) * 1000);
    char* tmp = res;
    Node* cur = root;

    for (int i = 0; ; i++) {
        char c = strs[0][i];

        // 有两个条件表示已经找到了最长公共前缀,要跳出循环
        // 1. cur->isEnd = 1,表示有一个字符串终止在了这个字符
        // 2. cur下有不止一个子节点非空
        if (cur->isEnd != 1 && getLinks(cur) == 1) {
            *tmp++ = c;
            cur = cur->links[c - 'a'];
            continue;
        }
        break;
    }
    *tmp = '\0';

    return res;
}
最后修改:2021 年 11 月 28 日
如果觉得我的文章对你有用,请随意赞赏