C++で各文字が少なくともK回出現する最長部分文字列を求めるアルゴリズム
問題概要
小文字の英字のみで構成される文字列 s が与えられます。このとき、すべての文字が k 回以上出現する最長の部分文字列 T の長さを求めるのが本問題の目的です。
例として、文字列が "ababbc"、k = 2 の場合を考えてみましょう。このとき答えは 5 となり、最長の部分文字列は "ababb" です。なぜなら、この部分文字列には a が 2 回、b が 3 回含まれており、すべての文字が 2 回以上出現しているからです。
解法のアプローチ
この問題は分割統治法(再帰)を用いて解くことができます。基本的な発想は、「出現回数が k 回未満の文字は答えとなる部分文字列に絶対に含まれない」ため、そのような文字を区切り文字として文字列を分割し、分割された各部分に対して再帰的に同じ処理を適用する、というものです。
アルゴリズムの手順
- 文字列
sと整数kを引数に取る再帰関数longestSubstring()を作成します。 - k = 1 の場合は、すべての文字が条件を満たすため、文字列の長さをそのまま返します。
- 文字列の長さが k 未満の場合は、条件を満たす部分文字列が存在しないため 0 を返します。
- サイズ 26 の配列
cを作成し、-1 で初期化します(各文字の出現回数を記録するため)。 - 文字列を走査し、各文字の出現回数をカウントします。
badCharを'*'で初期化します。- 配列
cを調べ、出現回数が 0 より大きく k 未満の文字が見つかれば、それをbadCharとして記録し、ループを抜けます。 badCharが'*'のまま(=すべての文字が k 回以上出現している)であれば、文字列全体が答えとなるためnを返します。- 元の文字列を
badCharで分割し、部分文字列の配列vを作成します。 - 各部分文字列に対して再帰的に
longestSubstring()を呼び出し、結果の最大値をansとして記録します。 ansを返します。
C++による実装例
以下に、上記のアルゴリズムを実装したC++のコードを示します。
#include <bits/stdc++.h>
using namespace std;
class Solution {
public:
vector <string> splitString(string s, char x){
string temp = "";
vector <string> res;
for(int i = 0; i < s.size(); i++){
if(s[i] == x){
if(temp.size())res.push_back(temp);
temp = "";
}
else temp += s[i];
}
if(temp.size())res.push_back(temp);
return res;
}
int longestSubstring(string s, int k) {
if(k == 1)return s.size();
if(s.size() < k )return 0;
vector <int> cnt(26, -1);
int n = s.size();
for(int i = 0; i < n; i++){
if(cnt[s[i] - 'a'] == -1) cnt[s[i] - 'a'] = 1;
else cnt[s[i] - 'a']++;
}
char badChar = '*';
for(int i = 0; i < 26; i++){
if(cnt[i] != -1 && cnt[i] < k){
badChar = i + 'a';
break;
}
}
if(badChar == '*')return n;
vector <string> xx = splitString(s, badChar);
int ans = 0;
for(int i = 0; i < xx.size(); i++)ans = max(ans, longestSubstring(xx[i], k));
return ans;
}
};
main(){
Solution ob;
cout << ob.longestSubstring("ababbc", 2);
}入力例
"ababbc" 2
出力例
5
計算量について
このアルゴリズムの計算量を簡単に考察しておきましょう。各再帰呼び出しでは、文字列の走査に O(n)、分割処理にも O(n) の時間がかかります。最悪の場合、分割が一度に 1 文字しか減らさないケースもありうるため、全体の時間計算量は O(n²) となります。ただし、実際には区切り文字で大きく分割されることが多いため、多くのケースで効率的に動作します。空間計算量は再帰の深さと分割された文字列の格納に依存し、最悪で O(n²) となります。
まとめ
本記事では、各文字が少なくとも k 回出現する最長部分文字列の長さを求める問題を、出現回数カウントと分割統治法を組み合わせて解く方法を解説しました。「条件を満たさない文字を区切りとして分割する」というシンプルな発想が、再帰的な解法の鍵となる好例です。
-
Pythonでk種類の一意な文字を含む最長部分文字列を求める方法【スライディングウィンドウ法】
問題の概要 文字列が与えられたとき、ちょうどk個の一意な(重複しない)文字を含む最長の部分文字列を返すことを考えます。条件を満たす最長の部分文字列が複数存在する場合は、そのうちのどれか1つを返せば問題ありません。 例えば、入力が s = ppqprqtqtqt、k = 3 の場合、出力は長さ7の「rqtqtqt」となります。 解法の考え方:スライディングウィンドウ法 この問題はスライディングウィンドウ(尺取り法)と呼ばれる手法で効率的に解けます。ウィンドウの右端を1文字ずつ伸ばしていき、一意な文字の種類数が制約を超えたら左端を縮める、という操作を繰り返すことで答えを求めます。 アルゴリズム
-
Pythonで重複文字を含まない最長部分文字列を求めるアルゴリズム
文字列が与えられたとき、同じ文字が繰り返されない最長の部分文字列(連続した部分)の長さを求める問題です。例えば、文字列が「ABCABCBB」である場合、答えは 3 になります。これは、重複する文字を一切含まない部分文字列として「ABC」(長さ3)が存在するためです。解法のアプローチこの問題は、スライディングウィンドウ(尺取り法)とハッシュマップを組み合わせることで効率的に解くことができます。各文字が最後に出現した位置を記録しながら、ウィンドウの左端を適切に移動させるのがポイントです。アルゴリズムの手順i := 0、j := 0 と初期化し、文字の出現位置を記録するためのマップ(辞書)を用意しま