题目

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

public class Solution {
    public String longestCommonPrefix(String[] strs) {
    }
}

Leetcode链接

https://leetcode.com/problems...

解题方法

两两比较法

假设我们现在有一个函数String lcs(String str1, String str2)可以求出两个字符串str1和str2的公共最长前缀,那么解题过程如下。

  1. 我们取出strs中的前两个字符串strs[0]和strs[1],计算出它们的公共最长前缀LCS1 = lcs(strs[0], strs[1])

  2. 再取出strs中的第三个字符串strs[2],计算出LCS1和strs[2]的公共最长前缀LCS2 = lcs(LCS1, strs[2]) = lcs(lcs(strs[0], strs[1]), strs[2])) = lcs(strs[0], strs[1], strs[2]),也就是说LCS2不仅是LCS1和strs[2]的公共最长前缀,也是strs[0], strs[1]和strs[2]的公共最长前缀

  3. 以此类推,假设strs的长度为4, 我们可以得到整个strs字符串数组的公共最长前缀LCS = lcs(lcs(lcs(strs[0], strs[1]), strs[2]), strs[3])

那么剩下的工作就是写出函数lcs,然后在主函数longestCommonPrefix中循环调用这个lcs函数就可以了。代码如下:

public class Solution {
    private String lcs(String str1, String str2) {
        int rightBoundary = 0;
        int minLength = Math.min(str1.length(), str2.length());
        while(rightBoundary < minLength && str1.charAt(rightBoundary) == str2.charAt(rightBoundary)) {
            rightBoundary++;
        }
        return str1.substring(0, rightBoundary);
    }
    public String longestCommonPrefix(String[] strs) {
        if(strs.length == 0) return "";

        String previousLongestCommonPrefix = strs[0];
        for(int i = 1; i < strs.length; i++) {
            previousLongestCommonPrefix = lcs(previousLongestCommonPrefix, strs[i]);
        }
        return previousLongestCommonPrefix;
    }
}

这种方法的时间复杂度为O(S),S为字符串数组所包含的字符总个数,空间复杂度为O(1)。

纵向比较法

这种方法的过程如下:

  1. 比较字符串数组strs中的每一个字符串的第一个字符,如果这个字符在每个字符串中均存在且相等,则进行下一步,否则退出,返回结果strs[0].substring(0, 0),也就是空字符串。

  2. 比较字符串数组strs中的每一个字符串的第二个字符,如果这个字符在每个字符串中均存在且相等,则进行下一步,否则退出,返回结果strs[0].substring(0, 1)

  3. 以此类推,检测第三个字符,第四个字符...

  4. 比较字符串数组strs中的每一个字符串的第n个字符,n为strs[0]的字符串长度,如果这个字符在每个字符串中均存在且相等,则第一个字符串就是最长公共子串,返回strs[0],否则退出,返回结果strs[0].substring(0, n)

可以看出,整个过程分为两个循环,第一个循环逐个遍历strs中第一个字符串的每个字符,第二个循环遍历strs中第二个至最后一个字符串,并检测每个字符串的对应位置的字符是否与第一个字符串对应位置的字符相等,如果全部相等,继续遍历下一个字符,否则返回结果,代码如下。

public class Solution {
    public String longestCommonPrefix(String[] strs) {
        if(strs.length == 0) return "";
        for(int i = 0; i < strs[0].length(); i++) {
            for(int j = 1; j < strs.length; j++) {
                if(i >= strs[j].length() || strs[0].charAt(i) != strs[j].charAt(i)) {
                    return strs[0].substring(0, i);
                }
            }
        }
        return strs[0];
    }
}

这种方法的时间复杂度为O(S),S为字符串数组所包含的字符总个数,空间复杂度为O(1)。

分治算法

这种算法的核心思想在于将字符串数组分为两部分,分别计算左半部分和右半部分的最长公共前缀LCS1和LCS2,然后再求出LCS1和LCS2的公共最长前缀就是我们所需要的答案。

那你的问题可能就是如何求出LCS1和LCS2。这里以LCS1举例,在计算LCS1的过程中,我们又需要将字符串数组strs的左半部分再分为两部分left[]和right[],分别求出left[]和right[]的最长公共字串leftLCS和rightLCS之后,再求出leftLCS和rightLCS的公共最长前缀就是我们所需要的LCS1。

那如何计算leftLCS呢?你可能已经想到了,计算leftLCS或rightLCS的方法就是再将leftLCS或rightLCS分成两部分,分别计算出这两部分的公共最长字串再合起来即可。

这样分下去我们一定会得到以下两种情况中的一个。

  1. 我们分出来的字符串数组的长度为1,此时直接返回这个唯一的字符串即可。

  2. 我们分出来的字符串数组的长度为2,此时求出这两个字符串的公共最长前缀即可。

代码如下:

public class Solution {
    private String longestCommonPrefix(String str1, String str2) {
        for(int i = 0; i < str1.length(); i++) {
            if(i >= str2.length() || str1.charAt(i) != str2.charAt(i)) {
                return str1.substring(0, i);
            }
        }
        return str1;
    }
    private String longestCommonPrefix(String[] strs, int lo, int hi) {
        if(lo == hi) {
            return strs[lo];
        }
        if(hi == lo + 1) {
            return longestCommonPrefix(strs[lo], strs[hi]);
        }
        int mid = lo + (hi - lo) / 2;
        String lcp1 = longestCommonPrefix(strs, lo, mid);
        String lcp2 = longestCommonPrefix(strs, mid + 1, hi);
        return longestCommonPrefix(lcp1, lcp2);
    }
    public String longestCommonPrefix(String[] strs) {
        if(strs.length == 0) return "";
        return longestCommonPrefix(strs, 0, strs.length - 1);
    }
}

这种方法的时间复杂度为O(S),S为字符串数组所包含的字符总个数,空间复杂度为O(m*logn),m为字符串数组strs的长度,n为每个字符串长度的平均值。

二分查找法

假设我们现在正在处理的字符串数组内容如下:

strs = [
    "leetcode",
    "leetcddd",
    "leet"
]

我们将整个字符串数组分为左右两个部分(注意不是上下两个部分),结果如下:

         |
strs = [ |
    "leet|code",
    "leet|cddd",
    "leet|"
         |
         |
 ]

假设strs左半部分的最长公共前缀为LCS1,右半部分的最长公共前缀为LCS2。在这里我们分两种情况讨论:

  1. 如果LCS1的长度等于左半部分每一个字符串的长度(例子中的长度为4)的话,那么整个字符串数组strs的最长公共前缀就是LCS1 + LCS2,也就是说,这个最长公共前缀的右边界由LCS2决定。

  2. 如果LCS1的长度小于左半部分每一个字符串的长度(例子中的长度为4)的话,那么整个字符串数组strs的最长公共前缀就是LCS1,在这种情况下我们不需要再计算LCS2。

那怎么计算LCS1或者LCS2呢?这里我们以LCS1为例,聪明的你可能已经想到了,和前面的过程一样,我们把strs的左半部分再分成左右两个部分,得到子字符串数组之后再继续进行切割,一直到我们所获得的子字符串数组中每一个字符串的长度均为1为止,如下所示:

      |
      |
    "l|eetcode",
    "l|eetcddd",
    "l|eet"
      |
      |

在这种情况下,我们看一下每一个数组的当前位置所对应的字符是否都一样,如果一样,就返回这个字符(如例子中返回的就是l),如果不一样,返回空字符串。

整个过程如下:

二分查找法

代码如下:

public class Solution {
    private boolean isSameChar(String[] strs, int pos) {
        if(pos >= strs[0].length()) return false;
        for(int i = 0; i < strs.length; i++) {
            if(pos >= strs[i].length() || strs[0].charAt(pos) != strs[i].charAt(pos)) {
                return false;
            }
        }
        return true;
    }
    private String longestCommonPrefix(String[] strs, int lo, int hi) {
        if(lo == hi) {
            return isSameChar(strs, lo) ? strs[0].substring(lo, lo + 1) : "";
        }
        int mid = lo + (hi - lo) / 2;
        String longestCommonPrefixOfLeft = longestCommonPrefix(strs, lo, mid);
        if(longestCommonPrefixOfLeft.length() == mid - lo + 1) {
            return longestCommonPrefixOfLeft + longestCommonPrefix(strs, mid + 1, hi);
        } else {
            return longestCommonPrefixOfLeft;
        }
    }
    public String longestCommonPrefix(String[] strs) {
        if(strs.length == 0) return "";
        return longestCommonPrefix(strs, 0, strs[0].length() - 1);
    }
}

这种算法的时间复杂度是O(S*logn), S为字符串数组所含字符的总个数,n为字符串数组中每一个字符串长度的平均值,空间复杂度为O(n)


searene
1 声望0 粉丝