0

0

LeetCode算法:最长公共前缀问题全面解析

霞舞

霞舞

发布时间:2025-12-29 12:13:02

|

214人浏览过

|

来源于php中文网

原创

在软件开发和算法学习的旅程中,我们经常会遇到各种各样的字符串处理问题。其中,寻找一组字符串的最长公共前缀是一个常见且基础的问题。这个问题不仅在日常编程中具有实用价值,也是算法面试中考察候选人基本功的经典题目。通过解决这个问题,可以有效提升我们对字符串操作、循环控制以及算法设计的理解和应用能力。本文将深入探讨LeetCode上的“最长公共前缀”问题,详细分析问题本质,并提供一个清晰、高效的C++解决方案,帮助读者彻底掌握这一算法。

关键要点

问题定义:理解最长公共前缀的准确含义。

算法思路:掌握如何迭代比较字符串,找到共同的前缀。

C++实现:学习如何用C++编写代码,解决最长公共前缀问题。

性能优化:分析算法的时间复杂度和空间复杂度,寻找优化方案。

边界条件处理:考虑输入为空或数组元素为空等情况。

最长公共前缀问题深度剖析

什么是最长公共前缀?

☞☞☞AI 智能聊天, 问答助手, AI 智能搜索, 免费无限量使用 DeepSeek R1 模型☜☜☜

leetcode算法:最长公共前缀问题全面解析

最长公共前缀(Longest Common Prefix,LCP)指的是一组字符串从起始位置开始,所有字符串都相同的那部分字符序列。例如,对于字符串数组 ["flower", "flow", "flight"],它们的最长公共前缀是 "fl",因为这是所有字符串都共享的最长起始子串。理解这一概念是解决问题的关键。如果字符串数组中没有公共前缀,则最长公共前缀为空字符串 ""。最长公共前缀问题旨在找到给定字符串数组中所有字符串的最长公共前缀。这个问题在数据压缩、信息检索和生物信息学等领域都有广泛的应用。准确理解最长公共前缀的概念是解决问题的首要步骤。

为了更好理解最长公共前缀,可以想象一个现实生活中的例子:假设你正在查找电话簿中一组以相同字母开头的名字。最长公共前缀就像这些名字共享的最长首字母组合。例如,如果电话簿中有 “李明”,“李雷”,“李丽”,那么最长公共前缀就是“李”。

为什么要重视最长公共前缀问题?

  • 算法基础:它是学习字符串处理和算法设计的基础。解决这类问题能帮助开发者更好地理解字符串操作和循环控制等基本编程概念。
  • 实际应用:在文件路径匹配、数据库查询和代码自动补全等实际应用场景中,经常需要用到最长公共前缀算法。
  • 面试准备最长公共前缀是算法面试中常见的考题,掌握它可以增加面试成功的几率。

理解最长公共前缀概念的重要性

在解决任何算法问题之前,确保完全理解问题的定义至关重要。对于最长公共前缀问题,这意味着要清楚认识到目标是找到一组字符串共享的最长起始子串。这种理解将直接影响到解决问题的思路和方法的选择。只有准确理解了问题的定义,才能更好地设计算法,并避免在实现过程中出现偏差。

让我们通过一些例子来加深理解:

  1. 示例1
    • 输入:["flower", "flow", "flight"]
    • 输出:"fl"
  2. 示例2
    • 输入:["dog", "racecar", "car"]
    • 输出:""
  3. 示例3
    • 输入:["apple", "apricot", "appetizer"]
    • 输出:"app"

通过这些例子,我们可以更直观地看到最长公共前缀的特点:它必须是所有字符串共享的起始子串,且长度尽可能长。掌握了这一概念,我们就可以更有信心地开始解决最长公共前缀问题。

问题分解与算法设计

为了有效地解决最长公共前缀问题,我们可以将问题分解为几个关键步骤,并设计相应的算法来逐一解决这些步骤。

LeetCode算法:最长公共前缀问题全面解析

首先,我们需要找到一个合适的起点。一个常见的策略是将数组中的第一个字符串作为初始的公共前缀。然后,我们将这个初始前缀与数组中的其他字符串逐一进行比较,不断调整前缀,使其满足所有字符串的共同前缀要求。这个过程可以概括为以下几个步骤:

  1. 选择初始前缀:将字符串数组中的第一个字符串作为初始的公共前缀
  2. 迭代比较:遍历字符串数组中的剩余字符串,将每个字符串与当前的公共前缀进行比较。
  3. 调整前缀:如果当前字符串不是以公共前缀开始,则缩短公共前缀的长度,直到当前字符串以缩短后的前缀开始,或者前缀为空。
  4. 更新前缀:如果成功找到更短的公共前缀,则更新当前的前缀。
  5. 返回结果:完成所有字符串的比较后,返回最终的公共前缀

在算法设计中,我们需要考虑一些关键的细节:

  • 空数组处理:如果输入的字符串数组为空,则最长公共前缀为空字符串 ""
  • 空字符串处理:如果数组中的任何一个字符串为空,则最长公共前缀也为空字符串 ""
  • 字符串比较:在比较字符串时,需要注意字符串长度的限制,避免越界访问。

为了更好地理解算法流程,我们可以使用伪代码来描述:

函数 findLongestCommonPrefix(字符串数组 strs):
    如果 strs 为空,则返回 ""

    prefix = strs[0]

    对于 i 从 1 到 strs 的长度 - 1,执行:
        当 strs[i] 不是以 prefix 开始,执行:
            如果 prefix 为空,则返回 ""
            prefix = prefix 的子串,从 0 到 prefix 的长度 - 1

    返回 prefix

这个伪代码清晰地描述了算法的核心逻辑:通过迭代比较和前缀调整,逐步缩小公共前缀的范围,最终找到最长公共前缀。在接下来的章节中,我们将把这个算法转化为实际的C++代码。

C++实现:高效的代码解决方案

现在,让我们将上述算法设计转化为实际的C++代码。

LeetCode算法:最长公共前缀问题全面解析

下面是一个高效的C++解决方案,用于解决最长公共前缀问题:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        if (strs.empty()) {
            return "";
        }

        string prefix = strs[0];
        for (size_t i = 1; i < strs.size(); ++i) {
            while (strs[i].find(prefix) != 0) {
                if (prefix.empty()) {
                    return "";
                }
                prefix.pop_back();
            }
        }
        return prefix;
    }
};

int main() {
    Solution sol;
    vector<string> strs = {"flower", "flow", "flight"};
    string result = sol.longestCommonPrefix(strs);
    cout << "Longest common prefix: " << result << endl;
    return 0;
}

代码解析

  1. 头文件:首先,我们包含了必要的头文件 iostreamstringvector,以便进行输入输出、字符串操作和使用动态数组。
  2. Solution类:我们将解决方案封装在名为 Solution 的类中,这是一个良好的编程实践,有助于代码的组织和维护。
  3. longestCommonPrefix函数:这是解决问题的核心函数。它接受一个字符串向量 strs 作为输入,并返回一个字符串作为结果。
  4. 空数组处理:在函数开始时,我们首先检查输入的字符串数组是否为空。如果为空,则直接返回空字符串 ""
  5. 选择初始前缀:我们将数组中的第一个字符串 strs[0] 作为初始的公共前缀
  6. 迭代比较:我们使用一个循环遍历数组中的剩余字符串,从第二个字符串开始。
  7. 调整前缀:对于每个字符串,我们使用 strs[i].find(prefix) != 0 检查当前字符串是否以公共前缀开始。如果不是,则执行以下操作:
    • 如果 prefix 为空,则表示没有公共前缀,直接返回 ""
    • 使用 prefix.pop_back() 缩短 prefix 的长度,移除最后一个字符。
  8. 返回结果:完成所有字符串的比较后,返回最终的 prefix,这就是最长公共前缀

代码亮点

  • 清晰易懂:代码结构清晰,逻辑简单,易于理解和维护。
  • 高效性:使用 string::find 函数进行字符串比较,具有较高的效率。
  • 鲁棒性:考虑了空数组和空字符串等边界条件,具有较好的鲁棒性。

通过这个C++代码示例,我们可以看到如何将算法设计转化为实际的代码解决方案。在接下来的章节中,我们将讨论如何进一步优化这个解决方案,以提高其性能。

优化算法:提升性能的关键策略

虽然上述C++解决方案已经能够有效地解决最长公共前缀问题,但我们仍然可以对其进行优化,以提高其性能,尤其是在处理大规模数据集时。以下是一些优化策略:

  1. 减少字符串比较次数

    • 二分查找:我们可以使用二分查找的思想来寻找最长公共前缀。首先,找到字符串数组中最短字符串的长度 minLength。然后,在 0minLength 的范围内进行二分查找,检查是否存在长度为 mid公共前缀。如果存在,则在 midminLength 的范围内继续查找;否则,在 0mid - 1 的范围内查找。这种方法可以减少字符串比较的次数,尤其是在公共前缀较短时。
  2. 优化字符串比较方法

    Vondy
    Vondy

    下一代AI应用平台,汇集了一流的工具/应用程序

    下载
    • 字符逐个比较:我们可以使用字符逐个比较的方法来检查是否存在长度为 mid公共前缀。对于每个 mid,我们只需要比较所有字符串的前 mid 个字符是否相同。这种方法避免了使用 string::find 函数,可以减少字符串比较的时间。

下面是使用二分查找和字符逐个比较方法优化的C++代码:

#include <iostream>
#include <string>
#include <vector>

using namespace std;

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        if (strs.empty()) {
            return "";
        }

        int minLength = strs[0].length();
        for (size_t i = 1; i < strs.size(); ++i) {
            minLength = min(minLength, (int)strs[i].length());
        }

        int low = 0, high = minLength;
        while (low <= high) {
            int mid = (low + high) / 2;
            if (isCommonPrefix(strs, mid)) {
                low = mid + 1;
            } else {
                high = mid - 1;
            }
        }

        return strs[0].substr(0, high);
    }

private:
    bool isCommonPrefix(vector<string>& strs, int len) {
        string prefix = strs[0].substr(0, len);
        for (size_t i = 1; i < strs.size(); ++i) {
            if (strs[i].substr(0, len) != prefix) {
                return false;
            }
        }
        return true;
    }
};

int main() {
    Solution sol;
    vector<string> strs = {"flower", "flow", "flight"};
    string result = sol.longestCommonPrefix(strs);
    cout << "Longest common prefix: " << result << endl;
    return 0;
}

代码解析

  1. minLength计算:首先,我们计算字符串数组中最短字符串的长度 minLength
  2. 二分查找:我们使用 lowhigh 变量来表示二分查找的范围,初始值为 0minLength
  3. isCommonPrefix函数:这个函数用于检查是否存在长度为 len公共前缀。它通过比较所有字符串的前 len 个字符是否相同来实现。
  4. 更新范围:如果存在长度为 mid公共前缀,则更新 low 的值为 mid + 1,表示在 midminLength 的范围内继续查找;否则,更新 high 的值为 mid - 1,表示在 0mid - 1 的范围内查找。
  5. 返回结果:完成二分查找后,返回 strs[0].substr(0, high),这就是最长公共前缀

性能分析

  • 时间复杂度:使用二分查找后,时间复杂度从 O(S) 降低到 O(S * log(N)),其中 S 是所有字符串的字符总数,N 是字符串数组中最短字符串的长度。
  • 空间复杂度:空间复杂度仍然为 O(1),因为我们只使用了常数级别的额外空间。

通过这些优化策略,我们可以显著提高算法的性能,尤其是在处理大规模数据集时。在实际应用中,我们可以根据具体情况选择合适的优化方法。

深入探讨C++字符串操作

C++字符串查找与比较

在解决最长公共前缀问题时,字符串的查找与比较是核心操作。C++提供了丰富的字符串操作函数,让我们深入探讨这些函数,并了解如何在算法设计中灵活应用它们。

LeetCode算法:最长公共前缀问题全面解析

在最初的解决方案中,我们使用了 string::find 函数来检查一个字符串是否以指定的公共前缀开始。这个函数返回子串在字符串中首次出现的位置。如果子串位于字符串的起始位置,则返回 0;否则,返回其他值。以下是 string::find 函数的详细说明:

size_t string::find (const string& str, size_t pos = 0) const;
  • str:要查找的子串。
  • pos:开始查找的位置,默认为 0
  • 返回值:如果找到子串,则返回子串首次出现的位置;否则,返回 string::npos

虽然 string::find 函数非常方便,但在某些情况下,它可能不是最高效的选择。例如,当我们需要频繁地检查一个字符串是否以指定的前缀开始时,使用 string::find 函数可能会导致不必要的性能开销。在这种情况下,我们可以使用其他方法来优化字符串比较操作。

其他字符串比较方法

  1. substr函数:使用 string::substr 函数来提取子串,并直接进行比较。

    string sub = strs[i].substr(0, prefix.length());
    if (sub == prefix) {
        // 字符串以 prefix 开始
    }
  2. 字符逐个比较:使用循环逐个比较字符串的字符。

    bool startsWith = true;
    for (size_t j = 0; j < prefix.length(); ++j) {
        if (strs[i][j] != prefix[j]) {
            startsWith = false;
            break;
        }
    }
    if (startsWith) {
        // 字符串以 prefix 开始
    }

如何选择合适的字符串比较方法?

  • 性能要求:如果性能是关键因素,则应避免使用 string::find 函数,并选择更高效的比较方法,如字符逐个比较。
  • 代码可读性:如果代码可读性更重要,则可以使用 string::find 函数或 string::substr 函数,因为它们更简洁易懂。
  • 具体场景:根据具体场景选择合适的比较方法。例如,如果需要查找子串在字符串中首次出现的位置,则只能使用 string::find 函数。

通过深入了解C++字符串操作函数,并根据具体场景选择合适的比较方法,我们可以编写出更高效、更具可读性的代码。

代码实例使用指南

代码实例使用指南

要运行本文提供的代码示例,您需要一个C++编译器。以下是一些常用的C++编译器:

  • GCC:GCC(GNU Compiler Collection)是一个免费的开源编译器套件,适用于多种操作系统,包括Linux、macOS和Windows。GCC可以通过命令行进行编译。
  • Clang:Clang是另一个免费的开源编译器,也适用于多种操作系统。Clang通常与LLVM(Low Level Virtual Machine)一起使用。
  • Visual Studio:Visual Studio是微软提供的集成开发环境(IDE),适用于Windows操作系统。Visual Studio包含一个C++编译器,并提供了图形化界面,方便代码的编写和调试。

选择一个C++编译器后,按照以下步骤编译和运行代码:

  1. 创建C++源文件:将代码复制到一个文本文件中,并将文件保存为 .cpp 扩展名,例如 lcp.cpp
  2. 打开命令行终端:打开命令行终端(例如,Linux的终端、macOS的终端或Windows的命令提示符)。
  3. 编译代码:使用C++编译器编译代码。以下是使用GCC编译代码的示例:

    g++ lcp.cpp -o lcp

    这个命令将 lcp.cpp 文件编译成一个可执行文件 lcp

  4. 运行代码:运行编译后的可执行文件。以下是运行 lcp 文件的示例:

    ./lcp

    这个命令将运行 lcp 文件,并输出结果。

如果您使用的是Visual Studio,可以按照以下步骤编译和运行代码:

  1. 创建C++项目:打开Visual Studio,创建一个新的C++项目。
  2. 添加源文件:将代码复制到源文件中,并将文件添加到项目中。
  3. 编译代码:点击“生成”菜单,选择“生成解决方案”,编译代码。
  4. 运行代码:点击“调试”菜单,选择“开始执行(不调试)”,运行代码。

在运行代码后,您将看到最长公共前缀的结果输出到控制台。通过这些步骤,您可以轻松地编译和运行本文提供的代码示例。

原始迭代法解决最长公共前缀:优缺点分析

? Pros

代码简洁易懂,逻辑清晰。

空间复杂度低,只需要常数级别的额外空间。

适用于小规模数据集。

? Cons

时间复杂度较高,需要多次比较字符串。

在公共前缀较短时,性能可能较差。

不适用于大规模数据集。

常见问题解答

如果输入的字符串数组为空,应该如何处理?

如果输入的字符串数组为空,那么最长公共前缀也应该为空。在代码中,我们需要首先检查数组是否为空,如果为空则直接返回空字符串 "",避免出现空指针异常。

如何处理字符串数组中存在空字符串的情况?

如果字符串数组中存在空字符串,那么最长公共前缀也应该为空。因为空字符串与其他任何字符串的最长公共前缀都是空字符串。在代码中,我们可以在迭代比较字符串时,检查当前字符串是否为空,如果为空则直接返回空字符串 ""。

在比较字符串时,如何避免越界访问?

在比较字符串时,我们需要确保比较的索引不超出字符串的长度。为了避免越界访问,我们应该始终迭代到较短字符串的长度。在代码中,我们可以在每次比较字符之前,检查索引是否小于两个字符串的长度。

相关问题探讨

除了迭代比较和二分查找,还有其他解决最长公共前缀问题的方法吗?

是的,除了迭代比较和二分查找,还有一些其他解决最长公共前缀问题的方法: 分治法: 思路:将字符串数组分成两个子数组,分别找到它们的最长公共前缀,然后将这两个最长公共前缀再求最长公共前缀,即为原数组的最长公共前缀。 优点:可以并行处理子数组,提高效率。 缺点:实现较为复杂。 字典树(Trie树): 思路:将所有字符串插入到字典树中,然后从根节点开始遍历,直到遇到分叉节点或到达某个字符串的结尾。从根节点到分叉节点或字符串结尾的路径上的字符,即为最长公共前缀。 优点:可以高效地处理多个字符串的最长公共前缀问题。 缺点:需要额外的空间来存储字典树。 在实际应用中,我们可以根据具体情况选择合适的算法。如果字符串数组较小,则迭代比较方法可能更简单易懂;如果字符串数组较大,则分治法或字典树可能更高效。理解这些不同的方法可以帮助我们更好地解决最长公共前缀问题。 以下是一个使用分治法解决最长公共前缀问题的C++代码示例: #include #include \ #include using namespace std; class Solution { public: string longestCommonPrefix(vector& strs) { if (strs.empty()) { return ""; } return longestCommonPrefixHelper(strs, 0, strs.size() - 1); } private: string longestCommonPrefixHelper(vector& strs, int left, int right) { if (left == right) { return strs[left]; } int mid = (left + right) / 2; string leftPrefix = longestCommonPrefixHelper(strs, left, mid); string rightPrefix = longestCommonPrefixHelper(strs, mid + 1, right); return commonPrefix(leftPrefix, rightPrefix); } string commonPrefix(const string& str1, const string& str2) { int minLength = min(str1.length(), str2.length()); for (int i = 0; i ain() { Solution sol; vector strs = {"flower", "flow", "flight"}; string result = sol.longestCommonPrefix(strs); cout

热门AI工具

更多
DeepSeek
DeepSeek

幻方量化公司旗下的开源大模型平台

豆包大模型
豆包大模型

字节跳动自主研发的一系列大型语言模型

WorkBuddy
WorkBuddy

腾讯云推出的AI原生桌面智能体工作台

腾讯元宝
腾讯元宝

腾讯混元平台推出的AI助手

文心一言
文心一言

文心一言是百度开发的AI聊天机器人,通过对话可以生成各种形式的内容。

讯飞写作
讯飞写作

基于讯飞星火大模型的AI写作工具,可以快速生成新闻稿件、品宣文案、工作总结、心得体会等各种文文稿

即梦AI
即梦AI

一站式AI创作平台,免费AI图片和视频生成。

ChatGPT
ChatGPT

最最强大的AI聊天机器人程序,ChatGPT不单是聊天机器人,还能进行撰写邮件、视频脚本、文案、翻译、代码等任务。

相关专题

更多
TypeScript类型系统进阶与大型前端项目实践
TypeScript类型系统进阶与大型前端项目实践

本专题围绕 TypeScript 在大型前端项目中的应用展开,深入讲解类型系统设计与工程化开发方法。内容包括泛型与高级类型、类型推断机制、声明文件编写、模块化结构设计以及代码规范管理。通过真实项目案例分析,帮助开发者构建类型安全、结构清晰、易维护的前端工程体系,提高团队协作效率与代码质量。

26

2026.03.13

Python异步编程与Asyncio高并发应用实践
Python异步编程与Asyncio高并发应用实践

本专题围绕 Python 异步编程模型展开,深入讲解 Asyncio 框架的核心原理与应用实践。内容包括事件循环机制、协程任务调度、异步 IO 处理以及并发任务管理策略。通过构建高并发网络请求与异步数据处理案例,帮助开发者掌握 Python 在高并发场景中的高效开发方法,并提升系统资源利用率与整体运行性能。

46

2026.03.12

C# ASP.NET Core微服务架构与API网关实践
C# ASP.NET Core微服务架构与API网关实践

本专题围绕 C# 在现代后端架构中的微服务实践展开,系统讲解基于 ASP.NET Core 构建可扩展服务体系的核心方法。内容涵盖服务拆分策略、RESTful API 设计、服务间通信、API 网关统一入口管理以及服务治理机制。通过真实项目案例,帮助开发者掌握构建高可用微服务系统的关键技术,提高系统的可扩展性与维护效率。

178

2026.03.11

Go高并发任务调度与Goroutine池化实践
Go高并发任务调度与Goroutine池化实践

本专题围绕 Go 语言在高并发任务处理场景中的实践展开,系统讲解 Goroutine 调度模型、Channel 通信机制以及并发控制策略。内容包括任务队列设计、Goroutine 池化管理、资源限制控制以及并发任务的性能优化方法。通过实际案例演示,帮助开发者构建稳定高效的 Go 并发任务处理系统,提高系统在高负载环境下的处理能力与稳定性。

51

2026.03.10

Kotlin Android模块化架构与组件化开发实践
Kotlin Android模块化架构与组件化开发实践

本专题围绕 Kotlin 在 Android 应用开发中的架构实践展开,重点讲解模块化设计与组件化开发的实现思路。内容包括项目模块拆分策略、公共组件封装、依赖管理优化、路由通信机制以及大型项目的工程化管理方法。通过真实项目案例分析,帮助开发者构建结构清晰、易扩展且维护成本低的 Android 应用架构体系,提升团队协作效率与项目迭代速度。

92

2026.03.09

JavaScript浏览器渲染机制与前端性能优化实践
JavaScript浏览器渲染机制与前端性能优化实践

本专题围绕 JavaScript 在浏览器中的执行与渲染机制展开,系统讲解 DOM 构建、CSSOM 解析、重排与重绘原理,以及关键渲染路径优化方法。内容涵盖事件循环机制、异步任务调度、资源加载优化、代码拆分与懒加载等性能优化策略。通过真实前端项目案例,帮助开发者理解浏览器底层工作原理,并掌握提升网页加载速度与交互体验的实用技巧。

102

2026.03.06

Rust内存安全机制与所有权模型深度实践
Rust内存安全机制与所有权模型深度实践

本专题围绕 Rust 语言核心特性展开,深入讲解所有权机制、借用规则、生命周期管理以及智能指针等关键概念。通过系统级开发案例,分析内存安全保障原理与零成本抽象优势,并结合并发场景讲解 Send 与 Sync 特性实现机制。帮助开发者真正理解 Rust 的设计哲学,掌握在高性能与安全性并重场景中的工程实践能力。

227

2026.03.05

PHP高性能API设计与Laravel服务架构实践
PHP高性能API设计与Laravel服务架构实践

本专题围绕 PHP 在现代 Web 后端开发中的高性能实践展开,重点讲解基于 Laravel 框架构建可扩展 API 服务的核心方法。内容涵盖路由与中间件机制、服务容器与依赖注入、接口版本管理、缓存策略设计以及队列异步处理方案。同时结合高并发场景,深入分析性能瓶颈定位与优化思路,帮助开发者构建稳定、高效、易维护的 PHP 后端服务体系。

532

2026.03.04

AI安装教程大全
AI安装教程大全

2026最全AI工具安装教程专题:包含各版本AI绘图、AI视频、智能办公软件的本地化部署手册。全篇零基础友好,附带最新模型下载地址、一键安装脚本及常见报错修复方案。每日更新,收藏这一篇就够了,让AI安装不再报错!

171

2026.03.04

热门下载

更多
网站特效
/
网站源码
/
网站素材
/
前端模板

精品课程

更多
相关推荐
/
热门推荐
/
最新课程
Java 教程
Java 教程

共578课时 | 82万人学习

国外Web开发全栈课程全集
国外Web开发全栈课程全集

共12课时 | 1万人学习

关于我们 免责申明 举报中心 意见反馈 讲师合作 广告合作 最新更新
php中文网:公益在线php培训,帮助PHP学习者快速成长!
关注服务号 技术交流群
PHP中文网订阅号
每天精选资源文章推送

Copyright 2014-2026 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号