最长公共子前缀
14. Longest Common Prefix
Write a function to find the longest common prefix string amongst an array of strings.
If there is no common prefix, return an empty string ""
.
Example 1:
Input: ["flower","flow","flight"]
Output: "fl"
Example 2:
Input: ["dog","racecar","car"]
Output: ""
Explanation: There is no common prefix among the input strings.
垂直扫描
使用一个公共指针,代表当前每个字符串的位置。取第一个字符串的作为模板进行匹配。从头选择该模板字符串的字符与剩余字符串同一个位置的字符一一匹配。如果匹配成功,则继续下一个字符。如果失败,则之前匹配成功的字符所形成的字符串就是最长的公共前缀。
public String longestCommonPrefix(String[] strs) {
if(strs == null || strs.length == 0){
return "";
}
StringBuilder result = new StringBuilder();
int strsSize = strs.length;
int firstSize = strs[0].length();
for(int i = 0; i < firstSize ; i++){
char c = strs[0].charAt(i);
for(int j = 1; j < strs.length; j++){
if(i >= strs[j].length()) return result.toString();
if(c != strs[j].charAt(i)) return result.toString();
}
result.append(c);
}
return result.toString();
}
水平扫描
水平扫描的思想是先求出第一个字符串和第二个字符串的最长前缀,然后求该前缀字符串和下一个字符串的前缀字符串。这样不断更新前缀串,直到遍历完了全部字符串得到的字符串就为这一组字符串的最长前缀串。
public String longestCommonPrefix(String[] strs) {
if (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;
}
分治法
从水平扫描的方法当中,我们可以发现仅仅只有单边操作,上例是从左往右扫描,而实际上,我们还能将从右往左扫描相结合。即两边同时想中间扫描。也就是所谓的分治法。时间复杂度为
public String longestCommonPrefix(String[] strs) {
if (strs == null || strs.length == 0) return "";
return longestCommonPrefix(strs, 0 , strs.length - 1);
}
private String longestCommonPrefix(String[] strs, int l, int r) {
if (l == r) {
return strs[l];
}
else {
int mid = (l + r)/2;
String lcpLeft = longestCommonPrefix(strs, l , mid);
String lcpRight = longestCommonPrefix(strs, mid + 1,r);
return commonPrefix(lcpLeft, lcpRight);
}
}
String commonPrefix(String left,String right) {
int min = Math.min(left.length(), right.length());
for (int i = 0; i < min; i++) {
if ( left.charAt(i) != right.charAt(i) )
return left.substring(0, i);
}
return left.substring(0, min);
}
二分法
我们可以从公共字符串前缀的角度思考。首先最短的字符串一定满足最小字符串。因此我们可以先找出长度最小的字符串作为模板,然后使用二分法将该模板切割成两部分。这样就可以直接拿分割的左部分去匹配所有字符串。若都能匹配成功,则分割右部分,那右部分的左部分进行匹配。
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;
int high = minLen;
while (low <= high) {
int middle = (low + high) / 2;
if (isCommonPrefix(strs, middle))
low = middle + 1;
else
high = middle - 1;
}
return strs[0].substring(0, (low + high) / 2);
}
private boolean isCommonPrefix(String[] strs, int len){
String str1 = strs[0].substring(0,len);
for (int i = 1; i < strs.length; i++)
if (!strs[i].startsWith(str1))
return false;
return true;
}
字典树(前缀树)
字典树的应用场景有很多。如搜索引擎的自动填充,单词的拼写检查,IP路线的查询等等。字典树的实质就是将前缀合并在一起。字典树的核心在于插入操作和遍历操作。我们选择一个字符串作为模版,该字符串不作为构造字典树的材料。
- 插入操作:插入的时候需要遍历整个字符串。对于字符串中的字符,如果原有的树中存在该字符串,则将该字符作为根节点,继续遍历下一个字符。如果原有的树中不存在着这个节点,则添加一个新的节点,同时将其视作为根节点,继续遍历。
- 遍历操作:本次遍历的目的是返回最长前缀。因此,当满足以下四个最长公共前缀的条件就退出遍历
- 遍历到了字典叶节点
- 遍历到的节点的子节点数不止一个
- 遍历到了模版字符串的最后一个字符
- 所遍历的字符不存在。
class Solution {
class TrieNode {
private TrieNode[] links;
private final int R = 26;//表示26个字母
private boolean isEnd;//表示是否为根节点
int size = 0;
public TrieNode() {
links = new TrieNode[R];
}
public void put(char ch, TrieNode node){
links[ch - 'a'] = node;
++size;
}
public int getLinks(){
return size;
}
public boolean containsKey(char ch) {
return links[ch - 'a'] != null;
}
public TrieNode get(char ch) {
return links[ch - 'a'];
}
public void setEnd() {
isEnd = true;
}
public boolean isEnd() {
return isEnd;
}
}
public class Trie {
private TrieNode root;
public Trie() {
root = new TrieNode();
}
public void insert(String word) {
TrieNode node = root;
for(int i = 0; i < word.length(); ++i){
char c = word.charAt(i);
if(!node.containsKey(c)){
node.put(c, new TrieNode());
}
node = node.get(c);
}
node.setEnd();
}
private String searchPrefix(String word) {
TrieNode node = root;
for(int i = 0; i < word.length(); ++i){
char c = word.charAt(i);
if(node.isEnd() || node.getLinks() > 1 || node.get(c) == null){
return word.substring(0, i);
}
node = node.get(c);
}
return word;
}
}
public String longestCommonPrefix(String[] strs) {
if(strs.length == 0) return "";
if(strs.length == 1) return strs[0];
Trie trie = new Trie();
for(int i = 1; i < strs.length; ++i){
trie.insert(strs[i]);
}
return trie.searchPrefix(strs[0]);
}
}