在Java中删除字符串中的重复项

28

我正在尝试迭代一个字符串,以删除其中的重复字符。

例如,字符串 aabbccdef 应变为 abcdef,字符串 abcdabcd 应变为 abcd

以下是我迄今为止做的:

public class test {

    public static void main(String[] args) {

        String input = new String("abbc");
        String output = new String();

        for (int i = 0; i < input.length(); i++) {
            for (int j = 0; j < output.length(); j++) {
                if (input.charAt(i) != output.charAt(j)) {
                    output = output + input.charAt(i);
                }
            }
        }

        System.out.println(output);

    }

}

怎样做是最好的?


4
你是想只合并重复的字符,还是完全删除所有重复的字符?也就是说,"abba" 应该变成 "aba" 还是 "ab"? - Alistair A. Israel
我认为给出的代码不会起作用...流程永远不会进入第二个循环 :) - shivarajan
50个回答

57

将字符串转换为字符数组,然后将其存储在LinkedHashSet中。这将保留您的顺序并删除重复项。可以按照以下方式进行:

String string = "aabbccdefatafaz";

char[] chars = string.toCharArray();
Set<Character> charSet = new LinkedHashSet<Character>();
for (char c : chars) {
    charSet.add(c);
}

StringBuilder sb = new StringBuilder();
for (Character character : charSet) {
    sb.append(character);
}
System.out.println(sb.toString());

我想我真的不能避免使用 StringBuilder 或者数组列表了...哎,谢谢。 - Ricco
@Rico:你也可以手动完成这个过程(比如创建一个合适长度的数组,将所有非重复元素放入其中,然后构建这个字符串),但是这种方法会更加繁琐,而 StringBuilder 真正是为构建字符串而生的。 - Paŭlo Ebermann
这也将删除第二个“f”,这可能是或可能不是OP想要的。 - Alistair A. Israel
使用 new StringBuilder(charSet.size()) 可以进行优化,避免对 StringBuilder 进行重新调整大小。 - Simon Forsberg

22

1
可以使用 Arrays.stream 进行简化。 - Sinux

8
尝试这个简单的解决方案:
public String removeDuplicates(String input){
    String result = "";
    for (int i = 0; i < input.length(); i++) {
        if(!result.contains(String.valueOf(input.charAt(i)))) {
            result += String.valueOf(input.charAt(i));
        }
    }
    return result;
}

2
回答不错,但每次运行 += 时,整个字符串都会被销毁并重新复制,导致不必要的低效率。此外,在循环的每次迭代中测试字符串的 length() 引入了低效率。循环的长度不会改变,因此您不必在每个字符上都进行检查。 - Eric Leschinski

6
我会使用LinkedHashSet来帮助解决问题。它可以去除重复项(因为我们使用了Set),同时保持顺序(因为我们使用了链表实现)。这种方法可能并不是最好的,但是比较简单易懂。
String s="aabbccdef";
Set<Character> set=new LinkedHashSet<Character>();
for(char c:s.toCharArray())
{
    set.add(Character.valueOf(c));
}

它并没有返回一个字符串。 - realPK

2
public class RemoveRepeated4rmString {

    public static void main(String[] args) {
        String s = "harikrishna";
        String s2 = "";
        for (int i = 0; i < s.length(); i++) {
            Boolean found = false;
            for (int j = 0; j < s2.length(); j++) {
                if (s.charAt(i) == s2.charAt(j)) {
                    found = true;
                    break; //don't need to iterate further
                }
            }
            if (found == false) {
                s2 = s2.concat(String.valueOf(s.charAt(i)));
            }
        }
        System.out.println(s2);
    }
}

2
这是对 Dave 的答案 的改进。
它使用 HashSet 而不是稍微更昂贵的 LinkedHashSet,并且重用 chars 缓冲区作为结果,消除了对 StringBuilder 的需求。
String string = "aabbccdefatafaz";

char[] chars = string.toCharArray();
Set<Character> present = new HashSet<>();
int len = 0;
for (char c : chars)
    if (present.add(c))
        chars[len++] = c;

System.out.println(new String(chars, 0, len));   // abcdeftz

2

Java 8有一个新的String.chars()方法,它返回一个字符串中的字符流。你可以使用流操作来过滤出重复的字符,像这样:

Java 8最初的回答有一个新的String.chars()方法,它返回字符串中的字符流。您可以使用流操作来过滤掉重复的字符,例如:

String out = in.chars()
            .mapToObj(c -> Character.valueOf((char) c)) // bit messy as chars() returns an IntStream, not a CharStream (which doesn't exist)
            .distinct()
            .map(Object::toString)
            .collect(Collectors.joining(""));

2
创建一个StringWriter对象。使用for循环中的charAt(i)方法遍历原始字符串。使用char类型的变量来保存最后一个charAt值。如果迭代时的charAt值等于该变量存储的值,则不添加到StringWriter中。最后,使用StringWriter.toString()方法获取字符串,并进行必要的操作。

我尝试过类似的东西,但不是 StringWriter.toString()。第一个循环将遍历输入字符串,如果该字符不存在于结果字符串中,则将其附加...但它没有起作用。 - Ricco

1

编写代码,无需使用任何额外缓冲区即可删除字符串中的重复字符。注意:一个或两个额外变量是可以的,但不能使用额外的数组。

import java.util.*;
public class Main{
    public static char[] removeDupes(char[] arr){
        if (arr == null || arr.length < 2)
            return arr;
        int len = arr.length;
        int tail = 1;
        for(int x = 1; x < len; x++){
            int y;
            for(y = 0; y < tail; y++){
                if (arr[x] == arr[y]) break;
            }
            if (y == tail){
                arr[tail] = arr[x];
                tail++;
            }
        }
        return Arrays.copyOfRange(arr, 0, tail);
    }

    public static char[] bigArr(int len){
        char[] arr = new char[len];
        Random r = new Random();
        String alphabet = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890!@#$%^&*()-=_+[]{}|;:',.<>/?`~";

        for(int x = 0; x < len; x++){
            arr[x] = alphabet.charAt(r.nextInt(alphabet.length()));
        }

        return arr;
    }
    public static void main(String args[]){

        String result = new String(removeDupes(new char[]{'a', 'b', 'c', 'd', 'a'}));
        assert "abcd".equals(result) : "abcda should return abcd but it returns: " + result;

        result = new String(removeDupes(new char[]{'a', 'a', 'a', 'a'}));
        assert "a".equals(result) : "aaaa should return a but it returns: " + result;

        result = new String(removeDupes(new char[]{'a', 'b', 'c', 'a'}));
        assert "abc".equals(result) : "abca should return abc but it returns: " + result;

        result = new String(removeDupes(new char[]{'a', 'a', 'b', 'b'}));
        assert "ab".equals(result) : "aabb should return ab but it returns: " + result;

        result = new String(removeDupes(new char[]{'a'}));
        assert "a".equals(result) : "a should return a but it returns: " + result;

        result = new String(removeDupes(new char[]{'a', 'b', 'b', 'a'}));
        assert "ab".equals(result) : "abba should return ab but it returns: " + result;


        char[] arr = bigArr(5000000);
        long startTime = System.nanoTime();
        System.out.println("2: " + new String(removeDupes(arr)));
        long endTime = System.nanoTime();
        long duration = (endTime - startTime);
        System.out.println("Program took: " + duration + " nanoseconds");
        System.out.println("Program took: " + duration/1000000000 + " seconds");

    }
}

如何阅读和讨论上述代码:
  1. 方法 removeDupes 接受一个名为 arr 的原始字符数组。
  2. arr 以“按值”方式作为原始字符数组返回。传入的 arr 在主成员方法 removeDupes 结束时被垃圾回收。
  3. 此算法的运行时复杂度为 O(n),更具体地说是 O(n+(small constant)),其中常数是原始字符数组中唯一字符的数量。
  4. copyOfRange 不会显著增加运行时复杂度,因为它只复制了少量常数项。名为 arr 的字符数组没有完全遍历。
  5. 如果将 null 传递给 removeDupes,则该方法返回 null。
  6. 如果传递空的原始字符数组或包含一个值的数组,则返回未修改的数组。
  7. 方法 removeDupes 的速度尽可能快,充分利用 L1 和 L2 缓存,因此 Branch redirects are kept to a minimum
  8. 2015 年发布的标准计算机应该能够在包含 5 亿个字符的原始字符数组中完成此方法,时间介于 15 到 25 秒之间。

解释这段代码的工作原理:

传入数组的第一部分被用作最终返回的唯一字符的存储库。在函数开始时,答案是:“0到1之间的字符”,即0到tail之间的字符。

我们在循环外定义变量y,因为我们想要找到数组索引第一次重复出现在我们的存储库中的位置。当找到重复项时,它会跳出并退出,y==tail返回false,并且不会将其添加到存储库中。

当我们正在查看的索引x在我们的存储库中没有表示时,我们将其提取并添加到尾部的存储库中,并增加尾部的值。

最后,我们返回0和tail之间的数组,长度应该小于或等于原始数组的长度。

程序员面试谈话题:

如果将y++更改为++y,程序会有不同的行为吗?为什么?

结尾的数组复制是否代表另一个“N”通过整个数组进行使运行时复杂度变为O(n*n)而不是O(n)? 为什么?

你能用.equals方法替换原始字符的双等号比较吗?为什么或者为什么不行?

这个方法是否可以更改,以便进行“按引用”而不是现在的“按值”替换?为什么或者为什么不行?

你能通过在“arr”的开头对唯一值存储库进行排序来增加此算法的效率吗?在哪种情况下会更有效?


1
对我来说,每个人都似乎过于努力完成这项任务。我们关心的只是如果有重复,它是否会复制每个字母的1个副本。然后,因为我们只关心这些字符是否相继重复,所以嵌套循环变得任意,因为你可以简单地将位置n与位置n + 1进行比较。然后,因为这仅在它们不同的时候复制,要解决最后一个字符,您可以将空格附加到原始字符串的末尾,或者只需将其复制到结果中的字符串的最后一个字符即可。
字符串removeDuplicate(String s){
    String result = "";

    for (int i = 0; i < s.length(); i++){
        if (i + 1 < s.length() && s.charAt(i) != s.charAt(i+1)){
            result = result + s.charAt(i);
        }
        if (i + 1 == s.length()){
            result = result + s.charAt(i);
        }
    }

    return result;

}

我刚意识到他的第二个例子表明,即使重复项不相邻,它也可以删除。因此,对于他/她想要实现的目标,这个解决方案是不正确的。 - Chris

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接