算法题 最长公共前缀

LeetCode 14. 最长公共前缀

问题描述

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

示例

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

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

算法思路

垂直扫描法

  1. 边界处理
    • 空数组直接返回空字符串
    • 单字符串数组返回该字符串本身
  2. 逐列比较
    • 以第一个字符串为基准,遍历其每个字符
    • 对于每个字符位置,检查数组中其他字符串的相同位置
    • 若出现字符不匹配或字符串长度不足,立即返回当前已匹配的前缀
  3. 提前终止
    • 当发现不匹配时立即终止扫描
    • 若完全匹配第一个字符串,则返回该字符串

代码实现

class Solution {
    /**
     * 查找字符串数组的最长公共前缀
     * 
     * @param strs 字符串数组
     * @return 最长公共前缀
     */
    public String longestCommonPrefix(String[] strs) {
        // 空数组处理
        if (strs == null || strs.length == 0) {
            return "";
        }
        // 单字符串情况
        if (strs.length == 1) {
            return strs[0];
        }
        
        // 以第一个字符串为基准
        String first = strs[0];
        // 遍历第一个字符串的每个字符
        for (int i = 0; i < first.length(); i++) {
            char c = first.charAt(i);
            // 检查后续字符串的相同位置
            for (int j = 1; j < strs.length; j++) {
                /* 
                 * 终止条件:
                 * 1. 当前字符串长度不足
                 * 2. 字符不匹配
                 */
                if (i >= strs[j].length() || strs[j].charAt(i) != c) {
                    return first.substring(0, i);
                }
            }
        }
        // 完全匹配第一个字符串
        return first;
    }
}

算法分析

  • 时间复杂度:O(S)
    • S 是所有字符串字符总数
    • 最坏情况:所有字符串完全相同 → 完整扫描第一个字符串
  • 空间复杂度:O(1)
    • 仅使用常数级别的额外空间

算法过程

输入strs = ["flower","flow","flight"]

  1. 取第一个字符串 "flower" 为基准
  2. 第0列:
    • f vs flow[0]=f → 匹配
    • f vs flight[0]=f → 匹配
  3. 第1列:
    • l vs flow[1]=l → 匹配
    • l vs flight[1]=l → 匹配
  4. 第2列:
    • o vs flow[2]=o → 匹配
    • o vs flight[2]=i → 不匹配
  5. 返回 "fl"

测试用例

public static void main(String[] args) {
    Solution solution = new Solution();
    
    // 测试用例1:标准示例
    String[] strs1 = {"flower","flow","flight"};
    System.out.println("Test 1: " + solution.longestCommonPrefix(strs1)); // "fl"
    
    // 测试用例2:无公共前缀
    String[] strs2 = {"dog","racecar","car"};
    System.out.println("Test 2: " + solution.longestCommonPrefix(strs2)); // ""
    
    // 测试用例3:完全匹配
    String[] strs3 = {"apple","apple","apple"};
    System.out.println("Test 3: " + solution.longestCommonPrefix(strs3)); // "apple"
    
    // 测试用例4:部分匹配
    String[] strs4 = {"abc","abd","ab"};
    System.out.println("Test 4: " + solution.longestCommonPrefix(strs4)); // "ab"
    
    // 测试用例5:空数组
    String[] strs5 = {};
    System.out.println("Test 5: " + solution.longestCommonPrefix(strs5)); // ""
    
    // 测试用例6:包含空字符串
    String[] strs6 = {"ab","","abc"};
    System.out.println("Test 6: " + solution.longestCommonPrefix(strs6)); // ""
    
    // 测试用例7:单元素数组
    String[] strs7 = {"hello"};
    System.out.println("Test 7: " + solution.longestCommonPrefix(strs7)); // "hello"
    
    // 测试用例8:不同长度匹配
    String[] strs8 = {"a","ab","abc"};
    System.out.println("Test 8: " + solution.longestCommonPrefix(strs8)); // "a"
}

关键点

  1. 基准选择

    • 以第一个字符串为扫描基准
    • 其他字符串只需与基准比较
  2. 终止条件

    • i >= strs[j].length():当前字符串长度不足
    • strs[j].charAt(i) != c:字符不匹配
  3. 高效性

    • 发现不匹配立即终止扫描
    • 避免不必要的后续比较

常见问题

  1. 为什么选择垂直扫描而不是水平扫描?
    垂直扫描在发现不匹配时能立即终止,而水平扫描需要完整比较所有字符串,垂直扫描在平均情况下更高效。

  2. 如何处理不同长度的字符串?
    通过 i >= strs[j].length() 检查字符串长度,确保不越界访问。

  3. 算法是否依赖第一个字符串?
    是,但这是合理的:

    • 公共前缀不可能比第一个字符串更长
    • 若第一个字符串很短,扫描会提前终止
  4. 最坏情况下的时间复杂度?
    当所有字符串完全相同时,需要扫描整个第一个字符串,时间复杂度为 O(n*m),其中 n 是数组长度,m 是第一个字符串长度。

  5. 能否用分治法或二分查找优化?
    可以,但复杂度相同:

    // 二分查找实现
    public String longestCommonPrefix(String[] strs) {
        if (strs == null || strs.length == 0) return "";
        int minLen = Integer.MAX_VALUE;
        for (String str : strs) 
            minLen = Math.min(minLen, str.length());
        
        int low = 1, high = minLen;
        while (low <= high) {
            int mid = (low + high) / 2;
            if (isCommonPrefix(strs, mid))
                low = mid + 1;
            else
                high = mid - 1;
        }
        return strs[0].substring(0, (low + high) / 2);
    }
    
    private boolean isCommonPrefix(String[] strs, int len) {
        String prefix = strs[0].substring(0, len);
        for (int i = 1; i < strs.length; i++)
            if (!strs[i].startsWith(prefix))
                return false;
        return true;
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值