C++で異なる部分列の数を数える:動的計画法による解法
問題の概要
2つの文字列 S と T が与えられたとき、S の部分列のうち T と一致するものが何通りあるかを数えるのがこの問題です。
ここでいう部分列(subsequence)とは、元の文字列から一部の文字(0個でも可)を取り除いて作られる新しい文字列のことであり、残った文字の相対的な順序は崩れません。たとえば「ACE」は「ABCDE」の部分列ですが、「AEC」は元の順序が保たれていないため部分列ではありません。
たとえば、入力が「baalllloonnn」と「balloon」の場合、T に一致する S の部分列は 36 通り存在します。
解法の考え方(動的計画法)
この問題は動的計画法(DP)を使うことで効率的に解けます。dp[i][j] を「s の先頭 i 文字から作れる部分列のうち、t の先頭 j 文字と一致するものの個数」と定義します。
遷移のルールは次のとおりです。
- s[i] == t[j] のとき:その文字を使う場合が dp[i-1][j-1]、使わない場合が dp[i-1][j] なので、両者を足した値になります。
- s[i] != t[j] のとき:その文字は使えないため、dp[i][j] = dp[i-1][j] となります。
- 初期条件:t が空文字列のときは選び方が常に 1 通り(何も取らない)なので dp[i][0] = 1。逆に s が空で t が空でない場合は 0 になります。
アルゴリズムの手順
- n := s の長さ、m := t の長さとし、添字の扱いを簡単にするため s と t の先頭に空白を付加します。
- (n+1) × (m+1) のサイズの DP テーブルを作成します。
- dp[0][0] := 1 とし、すべての行の 0 列目に 1 を設定します。
- i を 1 から n まで、j を 1 から m まで二重ループで回します。
- s[i] == t[j] ならば dp[i][j] := dp[i-1][j-1]
- その後、dp[i][j] := dp[i][j] + dp[i-1][j]
- 最終的に dp[n][m] を返します。
C++での実装例
以下が実際の C++ コードです。オーバーフロー対策として long long 型を使用しています。
#include <bits/stdc++.h>
using namespace std;
typedef long long int lli;
class Solution {
public:
int numDistinct(string s, string t) {
int n = s.size();
int m = t.size();
s = " " + s;
t = " " + t;
vector < vector <lli>> dp(n + 1, vector <lli> (m + 1));
dp[0][0] = 1;
for(int i = 1; i<= n; i++)dp[i][0] = 1;
for(int i = 1; i <= n; i++){
for(int j = 1; j <= m; j++){
if(s[i] == t[j]) dp[i][j] = dp[i - 1][j - 1];
dp[i][j]+= dp[i - 1][j];
}
}
return dp[n][m];
}
};
main(){
Solution ob;
cout << (ob.numDistinct("baalllloonnn", "balloon"));
}
入力
"baalllloonnn" "balloon"
出力
36
計算量
時間計算量は O(n × m)、空間計算量も DP テーブル分の O(n × m) です。なお、dp 配列を 1 次元に圧縮すれば、空間計算量を O(m) まで削減することも可能です。
-
文字列のすべてのサブシーケンス(部分列)を出力する3つの方法を解説
問題の概要この問題では、1つの文字列が与えられ、その文字列のすべての部分列(サブシーケンス)を出力することが求められます。部分列とは、元の文字列から一部の文字を削除して作られる文字列のことであり、文字の並び順を入れ替えることはできません。具体的な入出力例を見てみましょう。入力:xyz出力:x, y, z, xy, yz, xz, xyz解説:上記の例では、文字を削除するだけで部分列を生成しており、並べ替えは一切行っていません。たとえば「xz」は「xyz」から「y」を取り除いたものですが、「zx」のような順序の入れ替えは許されません。なお、長さnの文字列の部分列は(空文字列を含めれば)2n個存在
-
C++でソート済み配列の絶対値における異なる要素数を数える方法
配列(Array)とは、同じデータ型の要素を集めたデータ構造のことです。ソート済み配列とは、要素が昇順または降順に並べられた配列を指します。異なる要素数(distinct count)とは、配列内に重複して存在しない要素の数のことです。絶対値の異なる要素数(absolute distinct count)とは、各要素の絶対値(符号を無視した値)に着目したときの、異なる要素の数を意味します。この記事では、ソート済み配列における絶対値の異なる要素数を求めるプログラムを紹介します。つまり、配列の各要素の絶対値を考えた場合に、何種類の値が存在するかをカウントします。例を見てみましょう。入力 : [-3