[LeetCode][14]Longest Common Prefix解析 两种算法和底层源码的深入对比-Java实现

Posted 胖子程序员

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了[LeetCode][14]Longest Common Prefix解析 两种算法和底层源码的深入对比-Java实现相关的知识,希望对你有一定的参考价值。

Q:

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

A:

这题的大概意思就是说给你一组字符串找出其中最长的哪个通用的前缀出来。这个东西不难找,但是如何找的又快又好不简单。其实这题本来就是easy题,但是却让我联想到了《数据结构与算法分析》上的一道题目,那道题目是这样的:

给一个8900个字的字典,从中间找出类似abc、bbc、abb这样单词中只有一个字母不同的单词进行分类,最差的算法,用的大家都想到的遍历算法进行分类耗时90多秒,更新到第四种算法的时候只用4秒钟了(最后还有一种3秒钟的方法)。他用的最关键的改变就是把单词切出来一个字母然后存在TreeMap里,如果TreeMap有的话就在里面的size上+1,最后找出size大于2的值,当然我之前说过有一种3秒钟的方法就是使用的hashmap。

关于hashmap和treemap的区别我会在我的数据结构与算法分析复习的部分讲。如果有兴趣请关注我的博客。

那我们来想一想我们可不可以用这种方法增加速度呢?

首先我们需要找出前缀的范围,有三种选择:

1、找出最小的字符,遍历每个字母当作前缀。O(n)

2、找出第一个字符,遍历每个字母当作前缀O(1)

3、找出前两个字符的共同部分,遍历字母当作前缀O(min(a.length,b.length).

对于我们得到的前缀范围我们可以得到她们的数量size。

遍历size来分割接下来的字符串,比如说[0,1],[0,2],[0,3]这样O(size*n)

最后找出这么多字符中谁的size==总String数量并且她的值最大就是我们要的最长前缀了O(size)

这样我们最后最好的时间复杂度的情况应该是O(size*n)并且0<size<=n,size为整数,也就是说O(size*n)>O(n)




那么我们来看第二种方法:

首先使用前两个对比,得出通用前缀prefix。O(prefix.length)

使用通用前缀根接下来的遍历,得出跟prefix的通用前缀并赋值给他自己O(n)

最后返回prefix

她的时间复杂度是O(prefix.length*n)

O(prefix.length*n)和O(size*n)谁大谁小真的不好比较。我们来分析一下

如果size的确定使用第三种办法,那么应该是跟第二种方法中的第一次取得prefix相等即O(prefix.length)=O(min(a.length,b.length).

对于第二种方法之后的prefix.length肯定小于第一次的prefix.length。但是对于第一种方法而言第一次的prefix.length等于之后的size。选取第一种里面的1和2的话size肯定是大的。所以经过对比分析第二种方法时间复杂度更小。

所以我们使用第二种方法计算,代码如下:

	public static void main(String[] args){
		String[] strs= {"aa","a"};
		System.out.println(method(strs));
	}

	private static String method(String[] strs) {
		// TODO Auto-generated method stub
		if(strs.length==0)
            return "";
        String prefix =strs[0];
		for(int i = 1;i<strs.length;i++){
			//char thisc = prefix.charAt(0);
			if(prefix.length()>strs[i].length()){
				prefix = prefix.substring(0, strs[i].length());
			}
			for(int j = 0;j< prefix.length();j++){
				if(prefix.charAt(j)!=strs[i].charAt(j)){//自负不相等截取prefix
					prefix = prefix.substring(0, j);
				}
			}
		}
		return prefix;
	}
当然这个是原版代码,后来发现要5ms,我在leetcode上参考了一下,修改成了如下代码:

	public static void main(String[] args){
		String[] strs= {"aa","a"};
		System.out.println(method(strs));
	}

	private static String method(String[] strs) {
		// TODO Auto-generated method stub

		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);

			}
		}
		return prefix;
	}
这样耗时1ms,问题就在一个方法上String.indexOf,为什么这个方法效率那么高呢?

我们看一下源码:

static int indexOf(char[] source, int sourceOffset, int sourceCount,
            char[] target, int targetOffset, int targetCount,
            int fromIndex) {
        if (fromIndex >= sourceCount) {
            return (targetCount == 0 ? sourceCount : -1);
        }
        if (fromIndex < 0) {
            fromIndex = 0;
        }
        if (targetCount == 0) {
            return fromIndex;
        }

        char first = target[targetOffset];
        int max = sourceOffset + (sourceCount - targetCount);

        for (int i = sourceOffset + fromIndex; i <= max; i++) {
            /* Look for first character. */
            if (source[i] != first) {
                while (++i <= max && source[i] != first);
            }

            /* Found first character, now look at the rest of v2 */
            if (i <= max) {
                int j = i + 1;
                int end = j + targetCount - 1;
                for (int k = targetOffset + 1; j < end && source[j]
                        == target[k]; j++, k++);

                if (j == end) {
                    /* Found whole string. */
                    return i - sourceOffset;
                }
            }
        }
        return -1;
    }

默认参数:

value, 0, value.length,str.value, 0, str.value.length, 0

我们可以看出来这段代码的思路就是遍历到第一个字符开始然后对比,都相同就返回index否则返回-1.

那我们根据这个思路修改一下原本的代码试试。

	if(strs.length==0)
            return "";
        String prefix =strs[0];
		for(int i = 1;i<strs.length;i++){
			//if(prefix.length()>strs[i].length()){
//				prefix = prefix.substring(0, strs[i].length());
			//}
			int max= Math.min(prefix.length(), strs[i].length());
			int j = 0;
			for(;j< max&&prefix.charAt(j)==strs[i].charAt(j);j++){}
			prefix = prefix.substring(0, j);

		}
		return prefix;
最后我把源码改成这样也还有4ms,然后我仔细对比源码发现问题应该出在String.charat()上面了,代码如下:

 public char charAt(int index) {
        if ((index < 0) || (index >= value.length)) {
            throw new StringIndexOutOfBoundsException(index);
        }
        return value[index];
    }

里面有两个判断非常耗费时间,我们改成如下代码
if(strs.length==0)
            return "";
        String prefix =strs[0];
		for(int i = 1;i<strs.length;i++){
			//if(prefix.length()>strs[i].length()){
//				prefix = prefix.substring(0, strs[i].length());
			//}
			char[] str = strs[i].toCharArray();
			int max= Math.min(prefix.length(), str.length);
			int j = 0;
			for(;j< max&&prefix.charAt(j)==str[j];j++){}
			prefix = prefix.substring(0, j);

		}
		return prefix;

就变成了2ms,果然问题出在这里,如果我们不使用tochararray()方法的话就应该是1ms了,这说明我们的思路和算法都没有错,底层还有很多需要注意的东西



以上是关于[LeetCode][14]Longest Common Prefix解析 两种算法和底层源码的深入对比-Java实现的主要内容,如果未能解决你的问题,请参考以下文章

Leetcode 14. Longest Common Prefix

LeetCode OJ 14Longest Common Prefix

LeetCode:14. Longest Commen Prefix(Easy)

LeetCode OJ 14Longest Common Prefix

14. Longest Common Prefix

Leetcode 14 Longest Common Prefix