leetcode:活字印刷

題目來源:力扣

題目描述:

你有一套活字字模 tiles,其中每個字模上都刻有一個字母 tiles[i]。返回你可以印出的非空字母序列的數目。
====================================================
輸入:“AAB”
輸出:8
解釋:可能的序列爲 “A”, “B”, “AA”, “AB”, “BA”, “AAB”, “ABA”, “BAA”。
===================================================
提示:
1 <= tiles.length <= 7
tiles 由大寫英文字母組成

審題:

本題有點類似與排列組合中的排列問題,其中子集的大小可以爲任意大小但非空.本題主要的複雜性在於字符串中可能存在重複字符.

對於這類題目,大都可以使用回溯的思路解決,區別在於考慮每一步的可能選擇子集.如果字符串中不包含重複值,則我們的下一步可以選擇任意未被選中的字符,或者不選(在第一步我們必須選擇一個值,因爲最終結果字符串不能爲空).而由於存在重複字符,因此我們選擇第一個字符’A’與第二個字符’A’對最終的結果是沒有任何區別的,如果按照原有思路,則會存在重複計數.因此,對於存在重複字符的字符串,我們的每一步選擇只能在所有未被選擇且不重複的字符中選擇.

可以使用鍵值對結構保存字符串中每一個字符的個數,基於此,我們的每一步選擇即爲所有值不爲空的鍵,或者空集(在第一步不能選擇空);當我們選擇空集時,表示當前選擇結束,計數+1;

java算法:

class Solution {
    Map<Character, Integer> map = new HashMap<>();
    int totalNum;

    //當前可選的字符集合爲所有至不爲0的鍵,或者不選任何元素,直接返回

    private void dfs(boolean end){
        //如果構建結束,則計數+1
        if(end){
            totalNum++;
            return;
        }

        for(Character k: map.keySet()){
            if(map.get(k) != 0){
                System.out.println(k);
                map.put(k, map.get(k)-1); //入棧,該鍵對應的值減1
                dfs(false); //由於我們在該步選擇了字符k,所以選擇並未結束,傳入false
                map.put(k, map.get(k)+1); //退棧,該鍵對應的值加1
            }
        }
        
        //從第二步起,在每一步,我們均可以選擇空集,表示當前構造子集結束.
        dfs(true);
    }

    public int numTilePossibilities(String tiles) {
        //統計字符串中各字符數量
        for(int i = 0; i < tiles.length(); i++){
            char c = tiles.charAt(i);
            if(map.containsKey(c))
                map.put(c, map.get(c)+1);
            else
                map.put(c, 1);
        }

		//由於第一步不能選擇空集,因此第一步單獨考慮
        for(char c: map.keySet()){
            map.put(c, map.get(c)-1);
            dfs(false);
            map.put(c, map.get(c)+1);
        }
        return totalNum;
    }
}

更新:

在參考其他人關於此題的題解後,發現我最初的思路很不是很簡潔,存在很多可以優化的地方.
1.首先由於原始字符串僅有大寫字符組成,因此我們可是使用整數數組保存每一個字符出現的次數來代替代碼中的HashMap結構.經對比,使用整數數組能夠大幅減少程序執行時間.
2.其次,回溯的思路可以重新設計.我們每一步的可選集合爲所有值不爲0的鍵,在選完當前值後,我們可以終止選擇,此時總的方案數加1,我們也可以繼續進行下一個字符的選擇,此時我們遞歸調用,將當前選中鍵的值減1,在遞歸退出後,將該鍵對應的值加1.每次遞歸調用的結果爲當前剩餘字符中可能的選擇結果.如果當前剩餘字符爲空,則返回0.

class Solution {

    private int dfs(int[] charNum){
        int res = 0;
        for(int i = 0; i < charNum.length; i++){
            if(charNum[i] != 0){
                res++;
                charNum[i]--;
                res += dfs(charNum);
                charNum[i]++;
            }
        }
        return res;
    }

    public int numTilePossibilities(String tiles) {
        int[] charNum = new int[26];
        //統計字符串中各字符數量
        for(int i = 0; i < tiles.length(); i++){
            charNum[tiles.charAt(i)-'A']++;
        }
        return dfs(charNum);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章