在Java中检测String中重复字符的最有效方法是什么?

时间:2011-03-28 02:33:31

标签: java hashmap time-complexity

使用数据结构(HashMap)我能够做到。

这是代码:

import java.util.*;

class unique{
    public static void main(String[] args){
        HashMap<Character, Integer> charMap = new HashMap<Character, Integer>();
        boolean isNotUnique = false;
            for ( char loop : args[0].toCharArray() ){
            Integer freq = charMap.get( loop );
            charMap.put( loop, ( freq == null )? 1 : freq+1 );
            if ( charMap.get( loop ) > 1 )
            {
                isNotUnique = true;
            }
        }
            System.out.println ( isNotUnique );
    }
}

没有数据结构,我想出了一个直截了当的方法。 这有O(n ^ 2)

class unique
{
    public static void main(String[] args)
    {
        String inputString = args[0];
        System.out.println( isUnique( inputString ) );

    }

    private static boolean isUnique(String inputString) {
        String methodString = inputString;
        for ( int i = 0; i < inputString.length(); i++ )
        {
            for ( int j = i+1; j < inputString.length(); j++ )
            {
                if ( methodString.charAt( i ) == methodString.charAt( j ) )
                {
                    return false;
                }
            }
        }
        return true;
    }
}

我想知道是否有可能以O(n)时间复杂度来解决

3 个答案:

答案 0 :(得分:1)

角色的定义是什么? a-z A-Z或所有unicode?

如果字符串的长度非常大,例如一百万,则可以构建一个int数组,数组的长度是字符集的长度,数组将初始化为零。

之后,根据每个字符串遍历字符串:array [(int)char] ++,这样,你就可以很容易地找到从数组中出现字符的时间。

然而,短字符串不需要这样的方法。

这个方法是O(n)

答案 1 :(得分:1)

如果您需要支持代理字符对未表示的Unicode字符,则可以执行此操作:

private static boolean isUnique(String inputString) {
    long[] used = new long[1024];
    for (char c : inputString.toCharArray()) {
        if ((used[c >>> 6] & (1 << c)) > 0) {
            return false;
        }
        used[c >>> 6] |= 1 << c;
    }
    return true;
}

它使用位翻转来节省内存。它与使用一组布尔值基本相同:

private static boolean isUnique2(String inputString) {
    boolean[] used = new boolean[65536];
    for (char c : inputString.toCharArray()) {
        if (used[c]) {
            return false;
        }
        used[c] = true;
    }
    return true;
}

如果您只需要支持ASCII字符,则可以在任何一种情况下限制used的大小以减少所需的内存(因此long[4]boolean[256])。低于inputString的某个长度,执行n ^ 2检查可能比为此分配内存更快。理想情况下,你根据长度做两者的组合。

如果您需要支持所有可能的Unicode字符,则必须修改它以支持代理字符对。您可以使用Character.isHighSurrogate(c)检测它们。有关帮助,请参阅this page,并在Google上搜索更多详细信息。

答案 2 :(得分:1)

  

我想知道是否有可能解决O(n)时间复杂度:

有两个简单的解决方案O(N)及时:

  • HashSet方法的时间为O(N),空间为O(N),其中N为字符串长度。 (包含HashSet个不同字符的常规Java N将占用O(N)空格,并且具有相对较大的比例常数。)

  • 位数组方法的时间为O(N),空间为O(1),但O(1)为8K字节(如果使用boolean[]则为64K字节并且有一个相关的成本,即将大量内存添加到当时。

在所有情况下,这些都不是最佳答案。

  • 对于足够小的N,一个简单的O(N^2)嵌套循环将是最快的。 (并且它不使用额外的内存。)

  • 对于中等大小的N,在碰撞时使用重新散列的自定义散列表将优于HashSet或位数组方法。 HashSet方法将比位阵列方法更好。

  • 对于足够大的N,位阵列方法将是最快的并使用最少的内存。

(对于上面的内容,我假设输入字符串不包含任何重复字符。如果有,那么N的实际值将小于字符串长度。)


如果重复的字符检测需要处理UTF-16代理对,那么简单的方法是即时转码为Unicode代码点,并将数据结构更改为使用HashSet<Integer>,更大的位数组等

相反,如果可以限制输入字符集的大小,可以减小位数组的大小等。

这些调整会对小/中/大阈值可能下降的位置产生重大影响。