查找字符串中第一个非重复字符的3种方法

19-01-25 jdon
         

编写Java程序以查找字符串中的第一个非重复字符是编码测试的常见问题。由于字符串是各种编程面试中的热门话题,因此最好准备一些众所周知的问题,例如使用递归反转字符串,或检查字符串是否是回文。这个问题也属于同一类。在进入解决方案之前,让我们先了解这个问题。你需要编写一个函数,它接受一个字符串并返回第一个非重复的字符,例如在世界“hello”中,除了'l'都是非重复的,但'h'是第一个不重复的字符。同样,在单词“swiss”中,'w'是第一个不重复的字符。解决此问题的一种方法是创建一个表来存储每个字符的计数,然后选择第一个不重复的条目。要记住的关键是顺序,您的代码必须返回第一个非重复的字母。

顺便说一句,在本文中,我们将看到3个示例来查找字符串中的第一个非重复字符。我们的第一个解决方案使用LinkedHashMap来存储字符数,因为LinkedHashMap维护了插入顺序,我们按照它们在字符串中出现的顺序插入字符,一旦我们扫描了字符串,我们只需要迭代LinkedHashMap并选择值为1的条目。是的,这个解决方案需要一个LinkedHashMap和两个for循环。

我们的第二个解决方案是在时间和空间之间进行权衡,在一次传递中找到第一个不重复的字符。这次,我们使用了一个集合和一个列表来分别保持重复和非重复字符。一旦我们完成了对字符串的扫描,即O(N),我们就可以通过访问O(1)操作符列表来获取魔法字符。因为list是一个有序的集合,所以get(0)返回第一个元素。

我们的第三个解决方案也是类似的,但是这次我们使用了hashmap而不是linkedhashmap,我们再次遍历字符串以找到第一个非重复字符。在下一节中,我们将介绍这个编程问题的代码示例和单元测试。您还可以从Java编程语言中看到更多关于这些问题和问题的字符串访谈问题列表。

如何从字符串中查找第一个非重复字符

下面是查找给定字符串中第一个非重复字符的完整代码示例。这个程序有三种方法来查找第一个非重复字符。每个都使用自己的算法来完成这个编程任务。第一个算法在getFirstUnrepeatedChar(string str)方法中实现。它首先从给定的字符串中获取字符数组并循环遍历它,以构建一个哈希表,其中字符为键,其计数为值。在下一步中,它遍历LinkedHashMap以查找值为1的条目,这是第一个非重复字符,因为linkedhashmap保持插入顺序,我们从头到尾迭代字符数组。坏的部分是它需要两次迭代,第一次与字符串中的字符数成比例,第二次与字符串中的重复字符数成比例。在最坏的情况下,如果字符串末尾包含非重复字符,则需要2*n时间来解决此问题。

第二种查找第一个非重复或唯一字符的方法是在firstnonrepeatingchar(字符串字)上编码,此解决方案只需一次就可以在字符串中查找第一个非重复字符。它采用了经典的时空权衡技术。它使用两个存储来减少一次迭代,即标准空间与时间的权衡。由于我们分别存储重复字符和非重复字符,所以在迭代结束时,列表中的第一个元素是字符串中的第一个非重复字符。虽然您可以选择在字符串中没有非重复字符的情况下返回空字符串或空字符串,但此字符串比前一个稍好。

解决这个编程问题的第三种方法是用firstUnrepeatedCharacter(string word)方法实现。它与第一个非常相似,只是我们使用了hashmap而不是linkedhashmap。由于以后不能保证任何顺序,我们必须依赖原始字符串来查找第一个非重复字符。这是第三个解决方案的算法。第一步:扫描字符串并将每个字符的计数存储在hashmap中。第二步:遍历字符串并从映射中获取每个字符的计数。由于我们要从第一个字符到最后一个字符遍历字符串,当任何字符的计数为1时,我们将中断,这是第一个非重复字符。这里的顺序是通过再次遍历字符串来实现的。

import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;

/**
 * Java Program to find first duplicate, non-repeated character in a String.
 * It demonstrate three simple example to do this programming problem.
 *
 * @author Javarevisited
 */
public class Programming {
    
    /*
     * Using LinkedHashMap to find first non repeated character of String
     * Algorithm :
     *            Step 1: get character array and loop through it to build a 
     *                    hash table with char and their count.
     *            Step 2: loop through LinkedHashMap to find an entry with 
     *                    value 1, that's your first non-repeated character,
     *                    as LinkedHashMap maintains insertion order.
     */
    public static char getFirstNonRepeatedChar(String str) {
        Map<Character,Integer> counts = new LinkedHashMap<>(str.length());
        
        for (char c : str.toCharArray()) {
            counts.put(c, counts.containsKey(c) ? counts.get(c) + 1 : 1);
        }
        
        for (Entry<Character,Integer> entry : counts.entrySet()) {
            if (entry.getValue() == 1) {
                return entry.getKey();
            }
        }
        throw new RuntimeException("didn't find any non repeated Character");
    }


    /*
     * Finds first non repeated character in a String in just one pass.
     * It uses two storage to cut down one iteration, standard space vs time
     * trade-off.Since we store repeated and non-repeated character separately,
     * at the end of iteration, first element from List is our first non
     * repeated character from String.
     */
    public static char firstNonRepeatingChar(String word) {
        Set<Character> repeating = new HashSet<>();
        List<Character> nonRepeating = new ArrayList<>();
        for (int i = 0; i < word.length(); i++) {
            char letter = word.charAt(i);
            if (repeating.contains(letter)) {
                continue;
            }
            if (nonRepeating.contains(letter)) {
                nonRepeating.remove((Character) letter);
                repeating.add(letter);
            } else {
                nonRepeating.add(letter);
            }
        }
        return nonRepeating.get(0);
    }


    /*
     * Using HashMap to find first non-repeated character from String in Java.
     * Algorithm :
     * Step 1 : Scan String and store count of each character in HashMap
     * Step 2 : traverse String and get count for each character from Map.
     *          Since we are going through String from first to last character,
     *          when count for any character is 1, we break, it's the first
     *          non repeated character. Here order is achieved by going
     *          through String again.
     */
    public static char firstNonRepeatedCharacter(String word) {
        HashMap<Character,Integer> scoreboard = new HashMap<>();
        // build table [char -> count]
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            if (scoreboard.containsKey(c)) {
                scoreboard.put(c, scoreboard.get(c) + 1);
            } else {
                scoreboard.put(c, 1);
            }
        }
        // since HashMap doesn't maintain order, going through string again
        for (int i = 0; i < word.length(); i++) {
            char c = word.charAt(i);
            if (scoreboard.get(c) == 1) {
                return c;
            }
        }
        throw new RuntimeException("Undefined behaviour");
    }

}

查找第一个唯一字符的JUnit测试

下面是一些JUnit测试案例来测试每个方法。我们测试不同类型的输入,一个包含重复项,另一个不包含重复项。由于程序没有定义空字符串、空字符串的情况下要做什么,以及如果只包含重复项,则返回什么,所以您可以随意使用有意义的方式来做。

import static org.junit.Assert.*;
import org.junit.Test;

public class ProgrammingTest {

    @Test
    public void testFirstNonRepeatedCharacter() {
        assertEquals('b', Programming.firstNonRepeatedCharacter("abcdefghija"));
        assertEquals('h', Programming.firstNonRepeatedCharacter("hello"));
        assertEquals('J', Programming.firstNonRepeatedCharacter("Java"));
        assertEquals('i', Programming.firstNonRepeatedCharacter("simplest"));
    }

    @Test
    public void testFirstNonRepeatingChar() {
        assertEquals('b', Programming.firstNonRepeatingChar("abcdefghija"));
        assertEquals('h', Programming.firstNonRepeatingChar("hello"));
        assertEquals('J', Programming.firstNonRepeatingChar("Java"));
        assertEquals('i', Programming.firstNonRepeatingChar("simplest"));
    }

    @Test
    public void testGetFirstNonRepeatedChar() {
        assertEquals('b', Programming.getFirstNonRepeatedChar("abcdefghija"));
        assertEquals('h', Programming.getFirstNonRepeatedChar("hello"));
        assertEquals('J', Programming.getFirstNonRepeatedChar("Java"));
        assertEquals('i', Programming.getFirstNonRepeatedChar("simplest"));
    }
}

如果您可以增强这个测试用例来检查更多的场景,那么就去做吧。没有比写详细的、有创意的测试用例更能打动面试官的方法了,很多程序员都想不出或者根本不想写。

这就是如何在Java中找到字符串的第一个非重复字符。我们已经看到了解决这个问题的三种方法,尽管它们使用了非常相似的逻辑,但它们彼此不同。这个程序对于初学者掌握Java集合框架也是很好的。它让您有机会探索不同的映射实现,并了解hashmap和linkedhashmap之间的区别,以决定何时使用它们。