“不同”子序列,按照字母做某种划分

串的不同子序列数

int distinctSubseqII(string S) {
    const int MOD = 1e9 + 7;
    vector<int> endsWith(26, 0); // 扫描到S[i]时、以各字母结尾的子序列数
    for (char c : S) {
        // 原先子序列都扩展个c,有sum(endsWith[])个;还有1个新的字母c序列
        // (不用担心旧的字母c序列,它被扩展成cc序列,和新的字母c序列不冲突)
        endsWith[c - 'a'] = accumulate(begin(endsWith), end(endsWith), 1L) % MOD;
    }
    return accumulate(begin(endsWith), end(endsWith), 0L) % MOD;
}

循环字母串中,串的不同子串数

int findSubstringInWraproundString(string p) {
    // 找以各个字母结尾的最长子串长
    // 比如以d结尾的最长子串bcd长3,它就贡献了d、cd、bcd三个子串
    vector<int> endsWith(26);
    int len = 0;
    for (int i = 0; i < p.size(); i++) {
        if (i > 0 && (p[i] == p[i-1] + 1 || p[i] == p[i-1] - 25)) len++;
        else len = 1;
        int charIdx = p[i] - 'a';
        endsWith[charIdx] = max(endsWith[charIdx], len);
    }

    return accumulate(begin(endsWith), end(endsWith), 0);
}

短串列表中,长串的子序列数

可一个个判断。也可将短串按待匹配字母分桶。

int numMatchingSubseq(string S, vector<string>& words) {
    // 将word按待匹配char分桶,charToMatch=>{wordIdx,charIdxInWord}
    vector<pair<int, int>> buckets[128];
    for (int i = 0; i < words.size(); i++) {
        buckets[words[i][0]].push_back({i, 0});
    }
    
    for (char c : S) { // 待匹配字母c
        auto cBucket = buckets[c];
        buckets[c].clear();
        for (auto [wordIdx, charIdx] : cBucket) { // 桶中word都匹配掉c
            charIdx++;
            buckets[words[wordIdx][charIdx]].push_back({wordIdx, charIdx});
        }
    }
    // 全单词匹配的最后分到'\0'桶里
    return buckets[0].size();
}

是不是长串的子序列(长串不变,有很多短串要判断)

bool isSubsequence(string s, string t) {
    // follow up,预处理长串t,记录各字母出现的位置列表
    vector<vector<int>> pos(26);
    for (int i = 0; i < t.size(); i++) {
        pos[t[i] - 'a'].push_back(i);
    }

    int lastIdx = -1;
    for (char c : s) { // 应在各字母的位置列表穿梭前进
        auto &list = pos[c - 'a'];
        auto it = upper_bound(list.begin(), list.end(), lastIdx);
        if (it == list.end()) return false;
        lastIdx = *it;
    }
    return true;
}