计算至少出现一次前 K 个字母的子字符串

data structurec++programming更新于 2024/10/17 2:49:00

在此问题中,我们需要计算至少出现 1 次所有 K 个字符的子字符串。

在这里,我们将使用两种不同的方法来解决这个问题。第一种方法取给定字符串的所有子字符串,检查子字符串是否包含所有 K 个字符,并计算包含所有 K 个字符的子字符串。

第二种方法使用滑动窗口技术来解决问题。

问题陈述 - 我们给出了一个包含 N 个字符的字符串 alpha。此外,我们给出了 K,表示仅包含前 K 个字母字符多次出现的字符串。我们需要计算包含所有 K 个字符至少 1 次出现的子字符串总数。

示例

输入

alpha = "abcdda", K = 4;

输出

4

说明- 包含所有 4 个字符的子字符串为 'abcd'、'abcdd'、'abcdda' 和 'bcdda'。

输入

alpha = "abc", K = 5

输出

0

解释- 给定字符串中没有包含所有 5 个字符的子字符串。

输入

alpha = "ccbba"; K = 3;

输出

2

解释- 字符串"ccbba"和"cbba"包含所有 3 个字符。

方法 1

在此方法中,我们将遍历字符串以获取所有子字符串并将它们存储在列表中。之后,我们将从字符串列表中计算至少包含一次所有 K 个字符的字符串的数量。

算法

步骤 1 - 初始化"subStr"列表以存储所有子字符串。

步骤 2 - 开始遍历字符串。此外,使用嵌套循环从 1 到字符串大小 - p 进行迭代。

步骤 3 - 使用 substr() 方法从第 p 个索引获取长度等于 q 的子字符串。另外,将子字符串推送到"subStr"列表。

步骤 4- 用 0 初始化"result"以存储有效子字符串的数量。

步骤 5 - 开始遍历子字符串列表,并定义"freq"映射以存储当前字符串中字符的频率。另外,用 0 初始化"chars"以计算字符串中的不同字符。

步骤 6 - 开始遍历当前字符串。如果字符在映射中的频率为 0,则更新频率,并将"chars"值增加 1。

步骤 7 - 如果 chars 的值等于 K,则将"result"增加 1。

步骤 8 - 返回结果值。

示例

#include <bits/stdc++.h>
using namespace std;

int numberOfSubstrings(string alpha, int K) {
    // 查找 alpha 的所有子字符串
    vector<string> subStr;
    for (int p = 0; p < alpha.size(); p++) {
        for (int q = 1; q <= alpha.size() - p; q++) {
            // 获取从 p 到 q 的子字符串
            subStr.push_back(alpha.substr(p, q));
        }
    }
    // 计算包含所有 K 个字符的子字符串的数量
    int result = 0;
    for (int p = 0; p < subStr.size(); p++) {
        // 存储当前子字符串中字符的频率
        map<char, int> freq;
        // 存储完全不同的字符
        int chars = 0;
        // 遍历子字符串
        for (int q = 0; q < subStr[p].size(); q++) {
            // 如果字符在映射中不存在,则增加字符数
            if (freq[subStr[p][q]] == 0) {
                freq[subStr[p][q]]++;
                chars++;
            }
        }
        // 如果不同的字符与 K 相同,则字符串有效。
        if (chars == K) {
            result++;
        }
    }
    return result;
}
int main() {
string alpha = "abcdda";
int K = 4;
cout << "至少包含所有 K 个字符一次的子字符串的数量为 " << numberOfSubstrings(alpha, K);
return 0;
}

输出

至少包含一次所有 K 个字符的子字符串的数量为 4

时间复杂度 - O(N*N*M),其中 O(N*N) 是获取所有子字符串,O(M) 是遍历字符串。

空间复杂度 - O(N*N) 存储所有子字符串。

方法 2

在这种方法中,我们将使用滑动窗口技术来计算至少包含一次所有 K 个字符的子字符串的数量。

算法

步骤 1 - 初始化 'freq' 映射以存储字符频率,'left' 和 'right' 指针为 0,表示滑动窗口指针,'len' 为字符串长度,'cnt' 为 0存储字符串的数量。

步骤 2 - 进行迭代,直到"right"小于字符串长度。

步骤 3 - 增加映射中"right"索引处的字符的字符频率。

步骤 4 - 如果映射的大小等于 K,则意味着我们得到了包含所有 K 个字符的子字符串。因此,请遵循以下步骤。

步骤 4.1 - 进行迭代,直到映射的大小等于 K。

步骤 4.2 - 将"len - right"添加到"cnt"。

步骤 4.3 - 要从当前窗口中删除左侧字符,请减少其在映射中的频率。如果映射中字符的频率为 0,则从映射中删除该字符。

步骤 4.4- 在嵌套循环中增加"左"指针。

步骤 4.5 - 在主循环中增加"右"指针。

步骤 5 - 否则,增加"右"指针值。

步骤 6 - 返回"cnt"值。

示例

让我们通过示例输入了解滑动窗口技术如何解决问题。

输入 - "abcdaabc",K = 4

  • 第一个窗口将是包含所有 4 个字符的"abcd"。因此,我们可以说 (0, 3)、(0, 4)、(0, 5)、(0, 6) 和 (0, 7) 所有子字符串都包含所有 K 个字符。

  • 之后,从 1 到 4 的下一个窗口包含所有字符。因此,(1, 4)、(1,5)、(1, 6) 和 (1, 7) 所有子字符串都至少包含一次所有 K 个字符。

  • 这样,我们可以计算每个有效窗口的子字符串数量。

#include <bits/stdc++.h>
using namespace std;

int numberOfSubstrings(string alpha, int K) {
    // 用于存储字符的频率
    unordered_map<char, int> freq;
    int left = 0, right = 0, len = alpha.size(), cnt = 0;
    // 遍历字符串
    while (right < len) {
        // 更新字符频率
        freq[alpha[right]]++;
        // 如果映射的大小包含所有 K 个字符
        if (freq.size() == K) {
            // 遍历映射,直到映射大小为 k
            while (freq.size() == K) {
                // 添加所有有效子字符串。
                // 如果 (left, right) 包含所有 K 个字符,则 (left, right + 1)、(left + right + 2),... 也包含。
                cnt += len - right;
                // 更新字符频率
                freq[alpha[left]]--;
                // 如果字符出现频率为 0,则删除该字符。
                if (freq[alpha[left]] == 0)
                freq.erase(alpha[left]);
                // 从左侧移动到下一个字符
                left++;
            }
            // 增加右指针。
            right++;
            }
        // 将右指针增加 1
        else {
            right++;
        }
    }
    // 返回 cnt 的值
    return cnt;
}
int main() {
    string alpha = "abcdda";
    int K = 4;
    cout << "至少包含所有 K 个字符一次的子字符串的数量为 " << numberOfSubstrings(alpha, K);
    return 0;
}

输出

至少包含一次所有 K 个字符的子字符串的数量为 4

时间复杂度 - O(N),用于滑动窗口。

空间复杂度 - O(K),用于将频率存储在映射中。

程序员还可以使用数组来存储字符的频率,而不是使用映射,因为我们可以从数组中访问元素的时间比使用映射要短。为了进行更多练习,程序员尝试解决需要计算仅包含一次所有 K 个字符的字符串数量的问题。


相关文章