刷题笔记 2| 豆包MarsCode AI 刷题

31 阅读2分钟

问题描述

小U最近迷上了活字印刷,他拥有一套活字字模,其中每个字模上都刻有一个字母。给定一个字符串 tiles 表示字母序列,每个字母代表一个字模,小U想知道用这些字模最多可以印出多少种不同的非空字母序列。

注意:每个字模只能使用一次。


测试样例

样例1:

输入:tiles = "AAB"
输出:8

样例2:

输入:tiles = "ABC"
输出:15

样例3:

输入:tiles = "AAABBC"
输出:188

样例4:

输入:tiles = "AAABCBD"
输出:1265

思路

这个问题是一个排列组合问题,类似于经典的排列问题与组合问题的结合。为了更精确地控制字母的选择,考虑到字母顺序是无关的,只关心每种字母的出现次数,我们可以将问题转化为从各个字母的数量中选择一定数量的字母

  • 首先统计每个字母在给定字符串中的出现次数。这个统计可以通过一个哈希表或者数组来存储字母的频率

  • 使用回溯来生成所有可能的组合。每次从剩下的字母中选择一个字母,构建一个新的字母序列并递归处理。

  • 由于字母中可能存在重复字符,生成的字母序列也可能重复。因此,每次选择字母时,必须确保当前字母的组合是唯一的。

    • 可以使用一个unordered_set来避免重复选择相同字母。
  • 当所有字母都选择完毕或者没有剩余可选择的字母时,递归结束。

代码

// dic:表示字母的剩余可用数量
// remaining:表示剩余可以选择的字母的数量
// count:用来记录所有有效的非空字母序列的总数
void backtrack(const vector<int>& dic, int remaining, int& count) {
    // 当剩余的字母个数为0时,结束递归
    if (remaining == 0) return;

    // 用于记录当前层次的所有不同组合
    unordered_set<char> used;
    
    // 尝试每一个字母
    for (int i = 0; i < 26; ++i) {
        if (dic[i] > 0 && used.find(i) == used.end()) {
            // 当前字母未使用,并且还剩有该字母
            used.insert(i);  // 记录当前字母
            // 使用该字母并递归生成组合
            count++;
            vector<int> newDic = dic;
            newDic[i]--;  // 减少该字母的计数
            backtrack(newDic, remaining - 1, count);
        }
    }
}

int solution(string tiles) {
    // 首先统计每个字母在 tiles 中出现的次数
    vector<int> dic(26, 0);
    for (char c : tiles) {
        dic[c - 'A']++;
    }

    int count = 0;
    // 从整个字符串开始
    backtrack(dic, tiles.size(), count);

    return count;
}

int main() {
    cout << (solution("AAB") == 8) << endl;
    cout << (solution("ABC") == 15) << endl;
    cout << (solution("AAABBC") == 188) << endl;
    cout << (solution("AAABCBD") == 1265) << endl;
    return 0;
}