查詢字串中第一個非重複字元的3種方法

jdon發表於2019-01-25

編寫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之間的區別,以決定何時使用它們。

相關文章