题目
编写一个函数来查找字符串数组中的最长公共前缀。
如果不存在公共前缀,返回空字符串 “”。
示例 1:
输入: [“flower”,“flow”,“flight”]
输出: “fl”
示例 2:
输入: [“dog”,“racecar”,“car”]
输出: “”
解释: 输入不存在公共前缀。
说明:
- 所有输入只包含小写字母 a-z 。
解答
解法一:利用字典序
利用字符串排序时的字典序规则。
排序后,最小字符串和最大字符串的公共前缀即是所有字符串的公共前缀。
复杂度:O(m * nlogn) 的时间,O(1) 的空间
代码
class Solution {
public String longestCommonPrefix(String[] strs) {
if(strs == null || strs.length < 1) return "";
Arrays.sort(strs);
String min = strs[0];
String max = strs[strs.length - 1];
StringBuilder stb = new StringBuilder();
for(int i = 0; i < min.length(); i++) {
if(max.charAt(i) == min.charAt(i)) {
stb.append(max.charAt(i));
} else {
break;
}
}
return stb.toString();
}
}
结果
解法二:水平扫描
具体如下:
- 让公共前缀初始化为第一个字符串。
- 然后不断的向后迭代,不断地缩减公共前缀 prefix 的长度。
复杂度:O(m * n) 的时间,O(1) 的空间。
代码
class Solution {
public String longestCommonPrefix(String[] strs) {
if(strs == null || strs.length == 0) return "";
String prefix = strs[0];
for(int i = 1; i < strs.length; i ++) {
String str = strs[i];
while(str.indexOf(prefix) != 0) {
prefix = prefix.substring(0, Math.min(str.length(), prefix.length() - 1));
}
}
return prefix;
}
}
结果
解法三:垂直扫描
垂直扫描上述思路略有不同,垂直扫描比较的是每个字符串相同位置的字符是否相同。
从第一个位置开始迭代,一旦发现有任何一个字符串在当前位置的字符与其他的不同,那么此位置就是最大前缀的边界点。
复杂度:O(m * n) 的时间,O(1) 的空间。
代码
class Solution {
public String longestCommonPrefix(String[] strs) {
if(strs == null || strs.length == 0) return "";
StringBuilder res = new StringBuilder();
for(int i = 0; i < strs[0].length(); i ++) {
char ch = strs[0].charAt(i);
for(int j = 1; j < strs.length; j ++) {
String str = strs[j];
if(i >= str.length() || str.charAt(i) != ch) {
return res.toString();
}
}
res.append(ch);
}
return res.toString();
}
}
结果
解法四:二分法
类似于解法二,不同的是公共前缀的确定使用了二分法,每次都对当前的前缀长度加倍或减半。
主要思路:
- 先找到最短的字符串长度,因为最大公共前缀的长度不会超过最短的字符串长度 minLen 。
- 采用二分法:
- 如果当前的前缀是所有字符串的公共前缀,那么就尝试将当前的前缀长度加倍。
- 否则,让当前的前缀长度减半。
- 最后返回由左右边界确定的公共前缀即可。
复杂度:O(m * nlogn) 的时间,O(1) 的空间。
代码
class Solution {
public String longestCommonPrefix(String[] strs) {
if(strs == null || strs.length < 1) return "";
int minLen = strs[0].length();
for(int i = 1; i < strs.length; i ++) {
minLen = Math.min(minLen, strs[i].length());
}
int low = 1;
int high = minLen;
while(low <= high) {
int mid = (low + high) >>> 1;
if(isCommonPrefix(strs, mid)) {
low = mid + 1;
} else {
high = mid - 1;
}
}
return strs[0].substring(0, (low + high) >>> 1);
}
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;
}
}
结果
解法五:分治法
采用归并排序的思想,不断融合字符串的公共前缀。
复杂度:O(m * n) 的时间,O(m * logn) 的空间。
代码
class Solution {
public String longestCommonPrefix(String[] strs) {
if(strs == null || strs.length == 0) return "";
return merge(strs, 0, strs.length - 1);
}
private String merge(String[] strs, int start, int end) {
if(start == end) return strs[start];
int mid = (start + end) >>> 1;
String left = merge(strs, start, mid);
String right = merge(strs, mid + 1, end);
return mergePrefix(left, right);
}
private String mergePrefix(String s1, String s2) {
if(s1.equals(s2)) return s1;
int minLen = Math.min(s1.length(), s2.length());
for(int i = 0; i < minLen; i ++) {
if(s1.charAt(i) != s2.charAt(i)) return s1.substring(0, i);
}
return s1.substring(0, minLen);
}
}
结果
扩展解法:Trie 字典树
使用 Trie 数据结构,将单词存储在一颗多叉树上。
对 Trie 不太了解的同学可以看一下:Leetcode 208. 实现 Trie (前缀树)
对于本题,需要注意所有字符串的公共前缀必须满足的条件:
- 当前结点的 next 数组有效长度必须为 1 。(不能有分叉)
- 当前结点不是某一个字符串的结束点。(当前结点不是单词的结束结点)
复杂度:O(m * n) 的时间,O(m * n) 的空间。
代码
class Solution {
class Node {
boolean isWord;
int size;
Node[] next = new Node[26];
}
private Node root = new Node();
public String longestCommonPrefix(String[] strs) {
if(strs == null || strs.length == 0) return "";
for(int i = 0; i < strs.length; i ++) {
if(strs[i].length() == 0) return "";
addWord(strs[i]);
}
return getLongestPrefix(strs[0]);
}
private void addWord(String word) {
Node p = root;
for(int i = 0; i < word.length(); i ++) {
char ch = word.charAt(i);
if(p.next[ch -'a'] == null) {
p.next[ch - 'a'] = new Node();
p.size ++;
}
p = p.next[ch - 'a'];
}
p.isWord = true;
}
private String getLongestPrefix(String word) {
Node p = root;
for(int i = 0; i < word.length(); i ++) {
char ch = word.charAt(i);
if(p.size != 1 || p.isWord) {
return word.substring(0, i);
} else {
p = p.next[ch - 'a'];
}
}
return word;
}
}