我正在处理从字符串中删除重复字符的面试问题。
天真的解决方案实际上更难实现,即使用两个for循环来检查每个索引的当前索引。
我尝试了几次这个问题,第一次尝试只处理排序后的字符串,即aabbcceedfg
O(n)
。
然后我意识到我可以使用HashSet
。此解决方案的时间复杂度也是O(n)
,但使用了两个Java库类,例如StringBuffer
和HashSet
,这使得它的空间复杂性不那么好。
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的库,这些库在面试环境中完全无效。
答案 0 :(得分:3)
您无法提高复杂性,但您可以在保持相同复杂性的同时优化代码。
boolean[]
) - 只有65536个不同的字符,适合8Kb。每一位都意味着&#34;你是否在#34;。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();
}
答案 1 :(得分:0)
使用集合/地图时,不要忘记几乎所有方法都返回值。例如,Set.add
返回它是否实际添加。 Set.remove
返回它是否实际被删除。 Map.put
和Map.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());
}
}
请注意,在缺少至少一个字符的字符串上,这将很慢。