繁体   English   中英

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

[英]Optimal solution of removing duplicates from an unsorted string

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

天真的解决方案实际上更难实现,即使用两个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的库,这些库在面试环境中完全无效。

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

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

-

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

使用sets / maps时,不要忘记几乎所有方法都返回值。 例如, 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);
}

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

当然使用BitSet通过@ErwinBolwidt的建议是,即使在这种情况下更好的性能:

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代理项对,您的方法也能正常工作。

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

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

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

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

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM