从未排序的字符串中删除重复项的最佳解决方案

时间:2015-09-18 04:23:53

标签: java

我正在处理从字符串中删除重复字符的面试问题。

天真的解决方案实际上更难实现,即使用两个for循环来检查每个索引的当前索引。

我尝试了几次这个问题,第一次尝试只处理排序后的字符串,即aabbcceedfg O(n)

然后我意识到我可以使用HashSet。此解决方案的时间复杂度也是O(n),但使用了两个Java库类,例如StringBufferHashSet,这使得它的空间复杂性不那么好。

public static String duplicate(String s) {
    HashSet<Character> dup = new HashSet<Character>();
    StringBuffer string = new StringBuffer();

    for(int i = 0; i < s.length() - 1; i++) {
        if(!dup.contains(s.charAt(i))){
            dup.add(s.charAt(i));
            string.append(s.charAt(i));
        }
    }
    return string.toString();
}

我在想 - 这个解决方案最适合技术面试吗?如果它不是最优的,那么更好的方法是什么?

我为Google解决了这个问题的最佳解决方案,但是,大多数解决方案都使用了太多特定于Java的库,这些库在面试环境中完全无效。

3 个答案:

答案 0 :(得分:3)

您无法提高复杂性,但您可以在保持相同复杂性的同时优化代码。

  1. 使用BitSet而不是HashSet(甚至只是boolean[]) - 只有65536个不同的字符,适合8Kb。每一位都意味着&#34;你是否在#34;。
  2. 之前看过这个角色
  3. 将StringBuffer设置为指定的大小 - 一个非常小的改进
  4. 修正:你的for循环在i < s.length() - 1结束但它应该在i < s.length()结束,否则它会忽略字符串的最后一个字符。
  5. -

    public static String duplicate(String s) {
        BitSet bits = new BitSet();
        StringBuffer string = new StringBuffer(s.length());
    
        for (int i = 0; i < s.length(); i++) {
            if (!bits.get(s.charAt(i))) {
                bits.set(s.charAt(i));
                string.append(s.charAt(i));
            }
        }
        return string.toString();
    }
    

答案 1 :(得分:0)

使用集合/地图时,不要忘记几乎所有方法都返回值。例如,Set.add返回它是否实际添加。 Set.remove返回它是否实际被删除。 Map.putMap.remove返回之前的值。使用此功能,您无需再次查询该集合,只需更改为if(dup.add(s.charAt(i))) ...

从性能角度来看,第二个改进可能是将String转储到char[]数组并手动处理,而不是StringBuffer/StringBuilder

public static String duplicate(String s) {
    HashSet<Character> dup = new HashSet<Character>();
    char[] chars = s.toCharArray();

    int i=0;
    for(char ch : chars) {
        if(dup.add(ch))
            chars[i++] = ch;
    }
    return new String(chars, 0, i);
}

请注意,我们正在将结果写入我们正在迭代的相同数组中。这适用于结果位置永远不会超过迭代位置。

当然,@ ErwinBolwidt建议使用BitSet在这种情况下会更高效:

public static String duplicate(String s) {
    BitSet dup = new BitSet();
    char[] chars = s.toCharArray();

    int i=0;
    for(char ch : chars) {
        if(!dup.get(ch)) {
            dup.set(ch, true);
            chars[i++] = ch;
        }
    }
    return new String(chars, 0, i);
}

最后,为了完整性,Java-8 Stream API解决方案速度较慢,但​​可能更具表现力:

public static String duplicateStream(String s) {
    return s.codePoints().distinct()
            .collect(StringBuilder::new, StringBuilder::appendCodePoint,
                    StringBuilder::append).toString();
}

请注意,处理代码点比处理字符更好,因为即使对于Unicode代理项对,您的方法也能正常工作。

答案 2 :(得分:-1)

如果它是一个非常长的字符串,你的算法将花费大部分时间来丢弃字符。

使用长字符串(如书长)可以更快的另一种方法是简单地浏览字母表,查找每个字符的第一个匹配项并存储找到的索引。找到所有字符后,根据找到的字符串创建新字符串。

package se.wederbrand.stackoverflow.alphabet;

import java.util.HashMap;
import java.util.Map;

public class Finder {
    public static void main(String[] args) {
        String target = "some really long string"; // like millions of characters
        HashMap<Integer, Character> found = new HashMap<Integer, Character>(25);

        for (Character c = 'a'; c <= 'z'; c++) {
            int foundAt = target.indexOf(c);
            if (foundAt != -1) {
                found.put(foundAt, c);
            }
        }

        StringBuffer result = new StringBuffer();
        for (Map.Entry<Integer, Character> entry : found.entrySet()) {
            result.append(entry.getValue());
        }

        System.out.println(result.toString());
    }
}

请注意,在缺少至少一个字符的字符串上,这将很慢。