当前位置: 首页 > news >正文

力扣14.最长公共前缀-纵向扫描法

📋 题目描述

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

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

示例 1:

输入:strs = ["flower","flow","flight"] 输出:"fl"

示例 2:

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

提示:

  • 1 <= strs.length <= 200

  • 0 <= strs[i].length <= 200

  • strs[i]仅由小写英文字母组成

🧠 解题思路

核心思想:纵向扫描法

以第一个字符串为基准,逐个字符地与其他字符串比较相同位置的字符,直到发现不匹配或某个字符串结束。

算法步骤:

  1. 边界处理:如果数组为空或为null,直接返回空字符串

  2. 选取基准:以第一个字符串作为比较的基准

  3. 逐字符比较

    • 遍历基准字符串的每个字符

    • 对于每个位置,检查数组中所有字符串在该位置的字符

    • 如果发现不匹配或某个字符串已到末尾,立即返回当前已匹配的前缀

  4. 完全匹配:如果所有字符都匹配,返回整个基准字符串

💻 代码实现

java

class Solution { public String longestCommonPrefix(String[] strs) { // 边界情况处理 if(strs == null || strs.length == 0) { return ""; } // 以第一个字符串作为比较基准 String s0 = strs[0]; // 遍历基准字符串的每个字符 for(int j = 0; j < s0.length(); j++) { char c = s0.charAt(j); // 获取基准字符串当前位置的字符 // 遍历数组中的所有字符串 for(String s : strs) { // 注意:这里要先判断索引是否越界,再比较字符 if(j == s.length() || s.charAt(j) != c) { // 发现不匹配或字符串已结束,返回已匹配的前缀 return s0.substring(0, j); } } } // 所有字符都匹配,返回整个基准字符串 return s0; } }

🔍 执行过程详解

以输入["flower", "flow", "flight"]为例:

第1轮比较(j = 0):

  • 基准字符:s0.charAt(0) = 'f'

  • 检查所有字符串的第0个字符:

    • "flower"[0] = 'f'

    • "flow"[0] = 'f'

    • "flight"[0] = 'f'

  • 所有匹配,继续下一轮

第2轮比较(j = 1):

  • 基准字符:s0.charAt(1) = 'l'

  • 检查所有字符串的第1个字符:

    • "flower"[1] = 'l'

    • "flow"[1] = 'l'

    • "flight"[1] = 'l'

  • 所有匹配,继续下一轮

第3轮比较(j = 2):

  • 基准字符:s0.charAt(2) = 'o'

  • 检查所有字符串的第2个字符:

    • "flower"[2] = 'o'

    • "flow"[2] = 'o'

    • "flight"[2] = 'i'✗(不匹配!)

  • 发现不匹配,返回s0.substring(0, 2) = "fl"

最终结果:"fl"

⚡ 复杂度分析

时间复杂度:O(S)

  • 其中 S 是所有字符串中字符的总数

  • 在最坏情况下,算法会检查每个字符串的每个字符

  • 但通常情况下一旦发现不匹配就会提前结束,实际运行时间通常小于 O(S)

空间复杂度:O(1)

  • 只使用了常数级别的额外空间

  • 没有使用额外的数据结构

🎯 关键点解析

1.边界条件处理的顺序

java

// ❌ 错误写法(可能导致数组越界) if(c != s.charAt(j) || j == s.length()) // ✅ 正确写法(利用短路原则) if(j == s.length() || s.charAt(j) != c)

重要原则:先检查索引是否有效,再访问该索引处的元素。

2.charAt()方法

  • 用于获取字符串中指定位置的字符

  • 索引从0开始,最大索引为length()-1

  • 如果索引越界会抛出StringIndexOutOfBoundsException

3.substring()方法

  • 用于提取字符串的一部分

  • substring(0, j)提取索引0到j-1的字符(不包含j)

  • 当j=0时,返回空字符串""

🔄 其他解法对比

1.横向扫描法

public String longestCommonPrefix(String[] strs) { if(strs == null || strs.length == 0) return ""; String prefix = strs[0]; for(int i = 1; i < strs.length; i++) { while(strs[i].indexOf(prefix) != 0) { prefix = prefix.substring(0, prefix.length() - 1); if(prefix.isEmpty()) return ""; } } return prefix; }
  • 思路:将前一个字符串的公共前缀与后一个字符串比较,不断缩减前缀

  • 时间复杂度:O(S),其中S是所有字符串字符总数

2.分治法

public String longestCommonPrefix(String[] strs) { if(strs == null || strs.length == 0) return ""; return divideAndConquer(strs, 0, strs.length - 1); } private String divideAndConquer(String[] strs, int left, int right) { if(left == right) return strs[left]; int mid = (left + right) / 2; String leftPrefix = divideAndConquer(strs, left, mid); String rightPrefix = divideAndConquer(strs, mid + 1, right); return commonPrefix(leftPrefix, rightPrefix); } private String commonPrefix(String str1, String str2) { int minLength = Math.min(str1.length(), str2.length()); for(int i = 0; i < minLength; i++) { if(str1.charAt(i) != str2.charAt(i)) { return str1.substring(0, i); } } return str1.substring(0, minLength); }
  • 思路:将问题分解为子问题,合并结果

  • 时间复杂度:O(S)

  • 优点:适合并行处理

3.二分查找法

public String longestCommonPrefix(String[] strs) { if(strs == null || strs.length == 0) return ""; int minLength = Integer.MAX_VALUE; for(String str : strs) { minLength = Math.min(minLength, str.length()); } int left = 0, right = minLength; while(left < right) { int mid = (left + right + 1) / 2; if(isCommonPrefix(strs, mid)) { left = mid; } else { right = mid - 1; } } return strs[0].substring(0, left); } private boolean isCommonPrefix(String[] strs, int length) { String prefix = strs[0].substring(0, length); for(int i = 1; i < strs.length; i++) { if(!strs[i].startsWith(prefix)) { return false; } } return true; }
  • 思路:对前缀长度进行二分查找

  • 时间复杂度:O(S·log m),其中m是最短字符串长度

  • 优点:当字符串很长时效率更高

📝 常见错误及避免方法

错误1:忘记处理空数组

java

// ❌ 错误:如果strs为空,会抛出NullPointerException String s0 = strs[0]; // ✅ 正确:先检查数组是否为空或null if(strs == null || strs.length == 0) { return ""; }

错误2:索引越界

java

// ❌ 错误:可能先访问s.charAt(j)导致越界 if(s.charAt(j) != c || j == s.length()) // ✅ 正确:先检查索引是否有效 if(j == s.length() || s.charAt(j) != c)

错误3:没有考虑字符串长度不同的情况

java

// ❌ 错误:假设所有字符串长度相同 for(int j = 0; j < s0.length(); j++) { // 如果没有检查j == s.length(),当s较短时会越界 } // ✅ 正确:在比较字符前检查字符串长度 if(j == s.length() || s.charAt(j) != c)

🏆 总结

本文介绍的纵向扫描法是最直观且高效的解法,具有以下优点:

  1. 代码简洁:仅需10余行代码即可实现

  2. 逻辑清晰:逐字符比较的思路容易理解

  3. 效率高:一旦发现不匹配立即返回,避免不必要的比较

  4. 空间优:只使用常数级别的额外空间

关键技巧

  • 以第一个字符串为基准

  • 利用短路原则避免索引越界

  • 使用substring()返回已匹配的前缀

掌握这种方法不仅能解决最长公共前缀问题,其中的思想(逐位比较、提前终止)也能应用于其他字符串处理问题中。

http://www.jsqmd.com/news/274816/

相关文章:

  • 新写的launch文件不能用tab补全
  • 用ppt绘制新的形状
  • 20260120 - Linux驱动学习笔记:SPI子系统核心层到具体硬件驱动
  • 灵遁者诗歌:演员之镜 · 真实的演技
  • 从0到1成为大模型应用开发工程师:154万年薪岗位全解析
  • 【物理应用】滑块-曲柄机构Matlab仿真
  • Serv-U+cpolar 让文件远程访问像连 Wi-Fi 一样简单
  • 救命神器9个AI论文软件,自考学生轻松搞定毕业论文!
  • 【YOLO模型导出格式】大全
  • 【Science Advances】“安全可触”的低电压仿生人工肌肉,让机器人更柔、更轻、更安全
  • 世界棋局:国家、巨头与文明的AI竞赛以及星链的最新发展
  • 【粉丝福利社】驾驭Gemini 3与Nano Banana:人人都是AI产品创客
  • “超级工作站”的搭建,cpolar可成功内网穿透软件540!
  • 运算符
  • NLP技术视角下的论文优化:2026主流降重平台算法与效果深度横评 - 品牌观察员小捷
  • MCP协议:LLM智能体的“万能转接器“,解决“一模型一接口“痛点,建议收藏
  • 如何下载Spring源码 - 详解
  • Linux驱动学习:验证MasterDriverDevice三方匹配成功
  • 2. C语言核心语法 - 实践
  • 华为笔记本安装Ubuntu系统,声卡没有声音的处理
  • 基于MP2307设计一个12V到7.5V左右的开关电源
  • 必收藏!基于模板-定理图谱的LLM数学推理增强技术,性能提升超乎想象!
  • 必看!AI架构师珍藏手册:1.5万字深度解析如何把AI关进确定性系统笼子
  • AES加密密钥安全存储、iOS设备管理实现方式Kafka能够实时收集、处理和分析用户行为数据,从而生成动态更新的用户画像AES加密密钥安全存储
  • 中石化加油卡兑换有隐藏玩法,闲置卡这样处理超划算 - 京顺回收
  • ssm228图书预订 网上书城管理系统vue
  • 【GPR回归预测】基于双向长短期记忆神经网络结合高斯过程回归(BiLSTM-GPR)的多变量回归预测 (多输入单输出)附Matlab代码
  • App自动化测试环境搭建(详细版)
  • 文件操作与文件内容操作
  • 大数据领域数据中台的架构设计思路