C++ 字符串 模拟 力扣 14. 最长公共前缀 每日一题 题解


在这里插入图片描述
在这里插入图片描述

零、题目描述

题目链接:力扣 14. 最长公共前缀

题目描述:
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 ""。

示例 1:
输入:strs = [“flower”,“flow”,“flight”]
输出:“fl”

示例 2:
输入:strs = [“dog”,“racecar”,“car”]
输出:“”
解释:输入不存在公共前缀。

提示:
1 <= strs.length <= 200
0 <= strs[i].length <= 200
strs[i] 如果非空,则仅由小写英文字母组成

一、为什么这道题值得你花几分钟的时间看完?

这道题是字符串处理的经典入门题,更是面试中“考察基础编程能力”的高频题——它看似简单,却能全方位检验你的“模拟思维”“边界处理”和“代码优化意识”,是夯实字符串操作基础的绝佳案例。

题目核心价值

  • 模拟思维的“入门钥匙”:题目逻辑直观,无需复杂算法,却能有效训练“将自然语言描述转化为代码逻辑”的能力,是新手入门字符串题型的最佳起点。
  • 边界场景的“试金石”:空字符串、单元素数组、字符串长度不一致、无公共前缀等特殊情况,能充分检验代码的稳健性,避免“看似能过样例,实则漏洞百出”。
  • 多解法的“练兵场”:横向遍历、纵向遍历、排序法等多种思路均可解题,能让你对比不同实现的优劣,培养“一题多解”的思维习惯。
  • 工程化思维的“缩影”:类似“公共前缀查找”的场景在工作中高频出现(如文件路径匹配、接口参数校验、字典树构建),解题思路可直接迁移复用。
  • 面试的“基础门槛”:作为字符串题型的入门题,常出现在笔试第一题或面试热身环节,快速写出简洁、高效、无bug的代码,能给面试官留下“基础扎实”的良好印象。

面试考察的核心方向

  1. 模拟逻辑的清晰度:能否将“找公共前缀”的过程拆分为可执行的代码步骤,避免逻辑混乱。
  2. 边界条件的考虑:是否能覆盖所有特殊用例,体现代码的鲁棒性。
  3. 代码的简洁性与效率:能否用简洁的语法实现核心逻辑,同时关注时间/空间复杂度的优化。
  4. 字符串操作的熟练度:对字符串的截取、比较、长度获取等基础接口的使用是否熟练。
  5. 算法思路的拓展性:能否在基础解法上,提出更优的实现(如利用排序简化对比),体现思维的深度。

掌握这道题,既能夯实字符串操作的基础,又能培养“模拟思维”和“边界处理意识”,后续遇到更复杂的字符串问题(如子串匹配、字符串转换)时,能更从容地应对。

二、算法原理

最长公共前缀的核心定义是:所有字符串共有的、从第一个字符开始的连续子串。如果存在任意一个字符串不包含该子串,或子串长度超过某个字符串的长度,则该子串不是公共前缀。

解题的关键突破口是:通过“逐步缩小公共前缀范围”或“逐位验证公共字符”的方式,找到所有字符串的交集。以下是两种最经典、最易实现的算法思路:

1. 横向遍历(两两对比法)

核心思路:
将“多个字符串找公共前缀”转化为“两两字符串找公共前缀”的迭代过程。以第一个字符串为初始公共前缀,依次与后续字符串对比,更新公共前缀为两者的最长公共部分,直到遍历完所有字符串或公共前缀为空(提前终止),如下图👇:
在这里插入图片描述
步骤拆解

  1. 边界处理:若字符串数组为空,直接返回空字符串(你的代码默认数组非空,可补充优化);
  2. 初始化公共前缀:将第一个字符串作为初始前缀 ret
  3. 迭代更新前缀:遍历数组中剩余的每个字符串 strs[i],调用 findCommon 函数计算 retstrs[i] 的公共前缀,并更新 ret
  4. 返回最终的 ret

纵向遍历(逐位验证法)

核心思路:
先确定所有字符串的最短长度(公共前缀长度不可能超过最短字符串),再逐位验证所有字符串的对应位置字符是否相同。若某一位所有字符串都相同,则加入结果;若存在不同,则立即终止验证。

步骤拆解:

  1. 边界处理:若字符串数组为空,直接返回空字符串;
  2. 计算最短字符串长度 size(公共前缀的最大可能长度);
  3. 逐位验证:遍历每个字符位置 j(从0到 size-1):
    • 验证所有字符串的第 j 位是否相同(通过 i 遍历所有字符串对比);
    • 若所有字符串第 j 位相同,将该字符加入结果 ret
    • 若存在不同,立即终止遍历;
  4. 返回 ret
    在这里插入图片描述

大体意思是根据这两个算法思路独立去写代码,提升代码能力,并且这道题在模拟的写法上有很多种,只要能跑过就完美。

三、代码实现

横向遍历(两两对比法)

#include <vector>
#include <string>
#include <algorithm>
using namespace std;

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        // 边界处理:数组为空时返回空字符串
        if (strs.empty()) return "";
        string ret = strs[0];  // 以第一个字符串为初始公共前缀
        // 遍历后续所有字符串,迭代更新公共前缀
        for (int i = 1; i < strs.size(); i++) {
            ret = findCommon(ret, strs[i]);
            // 提前终止:若公共前缀为空,无需继续遍历
            if (ret.empty()) break;
        }
        return ret;
    }

    // 辅助函数:计算两个字符串的最长公共前缀
    string findCommon(string& s1, string& s2) {
        int i = 0;
        // 遍历到较短字符串的末尾,且字符相同则继续
        while (i < min(s1.size(), s2.size()) && s1[i] == s2[i]) {
            i++;
        }
        // 截取前i个字符作为公共前缀(i=0时返回空字符串)
        return s1.substr(0, i);
    }
};

代码细节与易错点:

  • 边界处理:要验证数组为空的判断,避免 strs[0] 越界。
  • 提前终止:当 ret 为空时,直接跳出循环,减少无效计算。
  • 辅助函数解耦:将“两两对比”逻辑抽离为独立函数,代码结构更清晰,可复用。
  • min 函数使用:需包含 <algorithm> 头文件,否则会编译报错。
  • substr 用法:s1.substr(0, i) 表示从索引0开始截取 i 个字符,i=0 时返回空字符串,无需额外处理。

2. 纵向遍历(逐位验证法)

#include <vector>
#include <string>
#include <climits>  // 用于 INT_MAX
#include <algorithm>
using namespace std;

class Solution {
public:
    string longestCommonPrefix(vector<string>& strs) {
        // 边界处理:数组为空时返回空字符串
        if (strs.empty()) return "";
        int size = INT_MAX;
        // 计算所有字符串的最短长度(公共前缀最大可能长度)
        for (int i = 0; i < strs.size(); i++) {
            size = min(size, (int)strs[i].size());
        }

        string ret = "";
        // 逐位验证每个字符位置
        for (int j = 0; j < size; j++) {
            int i = 0;
            // 验证所有字符串的第j位是否相同
            while (i < strs.size() - 1) {
                if (strs[i][j] != strs[i+1][j]) {
                    break;  // 有不同字符,终止当前位验证
                }
                i++;
            }
            // 若i遍历到最后一个字符串,说明当前位所有字符相同
            if (i == strs.size() - 1) {
                ret.push_back(strs[i][j]);
            } else {
                break;  // 存在不同字符,终止所有验证
            }
        }
        return ret;
    }
};

代码细节与易错点

  • 边界处理:要判断数组为空的判断,避免后续循环逻辑出错;
  • 最短长度计算:strs[i].size() 返回 size_t 类型,需强制转换为 int 避免类型不匹配;
  • 循环终止条件:i < strs.size() - 1 确保对比到最后一个字符串(通过 i 从0到 n-2,对比 ii+1);
  • 提前终止:一旦某一位存在不同字符,立即跳出所有循环,减少冗余计算;
  • 字符拼接:使用 ret.push_back() 高效添加字符,比 ret += strs[i][j] 性能更优。

3. 复杂度分析

算法时间复杂度空间复杂度核心优势
横向遍历O(n * k)O(1)逻辑清晰,代码解耦,易维护
纵向遍历O(n * k)O(1)提前终止场景更多,平均效率更高

n 为字符串数组的长度,k 为所有字符串的平均长度(或最短字符串的长度);

四、总结

核心考点回顾

  1. 模拟思维:将“找公共前缀”的自然逻辑,转化为“两两对比”或“逐位验证”的代码步骤,体现逻辑拆解能力;
  2. 边界处理:覆盖空数组、空字符串、字符串长度不一致等特殊情况,是代码鲁棒性的关键;
  3. 代码结构优化:横向遍历通过辅助函数解耦逻辑,纵向遍历通过最短长度限制循环范围,均体现了代码设计能力;
  4. 字符串操作:熟练使用 substrsize()push_back() 等基础接口,简化代码实现;
  5. 效率优化:通过提前终止遍历,减少无效计算,提升代码执行效率。

解题思路迁移

  • 公共子串/子序列问题:类似“找公共部分”的逻辑,可迁移到“最长公共子序列”“公共后缀查找”等问题;
  • 逐位验证思想:在“字符匹配”“格式校验”等场景中(如验证所有字符串是否以某个前缀开头),可复用逐位对比的逻辑;
  • 辅助函数解耦:将重复逻辑抽离为独立函数,是工程化开发中提升代码可读性和复用性的常用技巧。

常见错误总结

  1. 忽略空数组边界条件,导致 strs[0] 越界;
  2. 横向遍历未提前终止,当公共前缀为空时仍继续遍历,浪费性能;
  3. 纵向遍历中未计算最短长度,导致访问短字符串的越界索引;
  4. 类型不匹配:size_tint 混用,导致编译报错或逻辑错误。

五、下题预告

下一篇我们将攻克字符串题型中的经典难题——力扣 5. 最长回文子串!这道题是面试中的“高频重难点”,不仅考察字符串的遍历与匹配,更需要掌握“中心扩展法”“动态规划”等核心解题技巧,同时还要兼顾时间复杂度的优化。

无论是笔试中的算法题,还是面试中的思路阐述,这道题都能充分检验你的“逻辑推理能力”和“优化意识”。相信掌握了这道题,你对字符串问题的理解会再上一个台阶!

Doro 带着小花🌸来啦!🌸奖励🌸坚持啃完基础题的你!基础扎实才能走得更远,下一道题虽然有难度,但只要跟着思路一步步拆解,一定能攻克它~ 别忘了收藏本文,后续刷题时随时回顾知识点,也欢迎在评论区分享你的解题感悟,我们下道题不见不散!

在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值