令和7年秋期試験午後問題 問3

問3 プログラミング

⇱問題PDF
二つの列の最長共通部分列(Longest Common Subsequence)の長さを求めるアルゴリズムに関する次の記述を読んで,設問に答えよ。
 列X={x1,x2,…,xn}に対して,順序を保持して要素を抽出した列を部分列という。また,列の長さはその列の要素の個数で定義される。ここでは,各要素が文字である列を考える。例えば,X={"A","B","C","B","D","A","B"} のとき,図1に示すように{"B","C","D","B"}はXの部分列の例であり,その長さは4である。
pm03_1.png
 ある列Zが二つの列X,Y両方の部分列であるとき,ZをXとYとの共通部分列といい,共通部分列のうち長さが最大となるものを最長共通部分列という。最長共通部分列は複数通り存在する場合もあるが,その長さは一意に決まる。X={"A","B","C","B","D","A","B"},Y={"B","D","C","A","B","A"}の場合の共通部分列及び最長共通部分列の例を図2に示す。
pm03_2.png
 なお,共通部分列が存在しない場合,最長共通部分列は空の列となり,その長さは0である。
 最長共通部分列の長さは,2本のDNAの塩基配列間の類似度を測る目的などに用いられる。

〔最長共通部分列の長さを求めるアルゴリズム〕
 二つの列X,Yの最長共通部分列の長さを求めるアルゴリズムを考える。列X,Yそれぞれについて,先頭からn個,k個の要素を抽出した列をXn,Ykと表記し,列Xn,Ykそれぞれの末尾の要素をxn,ykと表記する。例えば,X={"A","B"}とすると,X3={"A","B","C"},x3="C"である。このとき,列Xnと列Ykとの最長共通部分列の中の一つをLCS(n,k),最長共通部分列の長さをLCSL(n,k)と表記する。なお,X0とY0は空の列であり,x0とy0は存在しない。
 ここで,xnとykとが一致しているか否かに着目して次の(1)~(3)に場合分けし,再帰的な関係を用いてLCSL(n,k)を求めることを考える。
  • xn=ykの場合を考える。例えば,xn=yk="A"とする。このとき,LCS(n,k)の末尾の要素は"A"となる。よって,LCS(n,k)は,列Xn-1,Yk-1の最長共通部分列LCS(n-1,k-1)の末尾に"A"を付加したものと一致する。したがって,LCSL(n,k)=LCSL(n-1,k-1)+1が成り立つ。
  • xn≠ykの場合を考える。例えば,xn="A",yk="B"とする。ここで,LCS(n,k)の末尾の要素は"A"又は"A"以外となる。LCS(n,k)の末尾の要素が"A"である場合は,列Ykから末尾の"B"を取り除いても最長共通部分列には影響しないので,LCS(n,k)はLCS(n,k-1)と一致する。一方,LCS(n,k)の末尾の要素が"A"でない場合は,列Xnから末尾の"A"を取り除いても最長共通部分列には影響しないので,LCS(n,k)はLCS(n-1,k)と一致する。よって,LCS(n,k)はLCS(n,k-1)又はLCS(n-1,k)のいずれかと一致する。したがって,LCSL(n,k)は,LCSL(n,k-1)とLCSL(n-1,k)のうちの最大値と一致する。
  • n=0又はk=0の場合,最長共通部分列は空の列となり,LCSL(n,k)=0である。
〔動的計画法を用いて最長共通部分列の長さを求めるアルゴリズム]
 (1)~(3)の再帰的な関係に従い,LCSL(n,k)を再帰的に計算することによって,列X,Yの最長共通部分列の長さを求めることができる。しかし,再帰的に計算するアルゴリズムでは,重複して同じ計算をすることによって時間計算量が大きくなり,非効率になる場合がある。そこで,重複して同じ計算をすることを避けるために,LCSL(n,k)の値を動的計画法によって求めることを考える。
 二つの列がX={"A","B","C","B","D","A","B"},Y={"B","D","C","A","B","A"} の場合,0≦n≦7かつ0≦k≦6に対するLCSL(n,k)の値を図3に示す。図3の左端2列はn(0~7)とそれに対応するxnを,上端2行はk(0~6)とそれに対応するykを表す。n=0のときのxn,k=0のときのykは存在しないので,"-"と表す。各要素は列Xn,と列Ykとの最長共通部分列の長さLCSL(n,k)の値を示している。
pm03_3.png
 図3の各要素の値は,〔最長共通部分列の長さを求めるアルゴリズム〕の(1)~(3)の再帰的な関係に従って求められる。
 まず,n=0又はk=0のときは(3)に対応するので,LCSL(n,k)=0である。
 それ以外の値について,例えば,図3の①~③は次のように値が決まる。
 ①について,x2=y1なので(1)に対応し,LCSL(2,1)=LCSL(1,0)+1である。LCSL(1,0)=0なので,LCSL(2,1)=1となる。
 ②について,x2≠y2なので(2)に対応し,LCSL(2,1)=1,LCSL(1,2)=0なので,LCSL(2,2)=1となる。
 ③について,X6≠y2なので(2)に対応し,LCSL(6,1)=1,LCSL(5,2)=2なので,LCSL(6,2)=2となる。
 図3の要素の値を全て計算することによって列X,Yの最長共通部分列の長さが4であると分かる。

〔動的計画法を用いて最長共通部分列の長さを求めるプログラム〕
〔動的計画法を用いて最長共通部分列の長さを求めるアルゴリズム〕に基づいて,二つの列の最長共通部分列の長さを求めるプログラムを考える。任意の二つの列をそれぞれ配列S,Tとして受け取り,動的計画法を用いて最長共通部分列の長さを求めるプログラムを図4に示す。ここで,配列の要素番号は0から始まり,整数型の二次元配列 lcsl は,行番号が0から配列Sの要素数sまでの(s+1)行,列番号が0から配列Tの要素数tまでの(t+1)列の大きさをもつ。
pm03_4.png
 図4のプログラムの時間計算量を,配列Sの要素数s, 配列Tの要素数tを用いて表すとO()である。

設問1

列{"A","C","B","C","D","C"}と列{"C","D","B","D","C","A"}との最長共通部分列の長さを答えよ。

解答例・解答の要点

4

解説

本問では部分列を「元の列に対して、順序を保持して要素を抽出した列」と定義しています。順序さえ保っていれば、元の列中で連続しているかどうかは問いません。

設問1では、次の2つの列について最長共通部分列の長さを求めます。
  • A,C,B,C,D,C … 列❶
  • C,D,B,D,C,A … 列❷
列が短いので、長いほうから順に共通部分列が作れるか見ていくと判断しやすくなります。

まず長さ6の共通部分列が存在するには、両列が同一である必要があります。これは一目見ただけで違うとわかります。

次に長さ5を考えます。
長さ6の列から長さ5の部分列を作ることを考えると、必然的に1又は2文字目の要素を先頭としなければなりません。すなわち列❶では"A"又は"C"始まり、列❷では"C"又は"D"始まりに限られます。共通する可能性があるのは"C"で始まる文字列で、列❶では「C,B,C,D,C」がこれに該当します。列❷では"C"で始まり"C"で終わる部分列は「C,D,B,D,C」しかなく、両者は一致しません。よって、長さ5の共通部分列も存在しません。

同様の考え方で、続いて長さ4を考えます。
長さ6の列から長さ4の部分列を作ることを考えると、1~3文字目いずれかの要素を先頭としなければなりません。すなわち列❶では"A"、"C"又はB"始まり、列❷では"C"、"D"又は"B"始まりに限られます。したがって共通部分列の候補となるのは、"B"と"C"から始まる部分列です。
"B"から始まる長さ4の文字列は両列に一つずつあり、列❶では「B,C,D,C」、列❷の「B,D,C,A」ですが、これは一致しません。"C"から始まる長さ4の部分列は複数作れますが、照合していくとその中で「C,B,D,C」が共通部分列となることがわかります。
  • A,CB,C,DC … 列❶
  • C,D,BDC,A … 列❷
以上より、最長共通部分列の長さは4です。

∴4

設問2

図3中のに入れる適切な数値を答えよ。

解答例・解答の要点

ア:2
イ:4

解説

本文のアルゴリズムの説明と①~③の例を参照すると、表の各マスの値は次の規則で求められることがわかります。
❶xn = yk の場合
LCSL(n-1, k-1) + 1
つまり、左上マスの値+1
❷xn ≠ yk の場合
LCSL(n, k-1) と LCSL(n-1, k) のうちの最大値
つまり、左マスの値と上マスの値の大きいほう
について〕
空欄部分は LCSL(3, 4) に当たります。x3="C"、y4="A"で文字が異なるため、❷のパターンに該当します。計算には左マスと上マスの値が必要なため、関連する空欄を順に確定させていきます。
LCSL(1, 3)
x1="A"、y3="C"なので❷です。よって、左マス:0、上マス:0のうち大きい「0」が入ります
LCSL(2, 3)
x2="B"、y3="C"なので❷です。よって、左マス:1、上マス:0のうち大きい「1」が入ります
LCSL(3, 3)
x3=y3="C"なので❶です。よって、左上マス:1に1を加えた「2」が入ります
LCSL(1, 4)
x1=y4="A"なので❶です。よって、左上マス:0に1を加えた「1」が入ります
LCSL(2, 4)
x1="B"、y4="A"なので❷です。よって、左マス:0、上マス:1のうち大きい「1」が入ります
LCSL(3, 4) は❷のパターンなので、左マス:2、上マス:1のうち大きい「2」が入ります。

=2
pm03_5.png
について〕
空欄部分は LCSL(7, 5) に当たります。x7="B"、y5="B"で文字が同じなので、❶のパターンに該当します。計算には左上マスの値が必要となります。
LCSL(4, 3)
x4="B"、y3="C"なので❷です。よって、左マス:1、上マス:2のうち大きい「2」が入ります
LCSL(5, 3)
x5="D"、y3="C"なので❷です。よって、左マス:2、上マス:2のうち大きい「2」が入ります
LCSL(6, 4)
x6=y4="A"なので❶です。よって、左上マス:2に1を加えた「3」が入ります
LCSL(7, 5) は❶のパターンなので、左上マスの値に1を加えた「4」が入ります。

=4
pm03_6.png

設問3

図4中のに入れる適切な字句を答えよ。

解答例・解答の要点

ウ:lcsl[n-1,k-1] + 1
エ:lcsl[n,k-1]
オ:lcsl[n-1,k]
カ:lcsl[s, t]

解説

図4は、図3の表を順に埋めていくプロセスをプログラムとして実装したものです。解説の便宜上、行番号を振っておきます。
pm03_7.png
について〕
行15は〔動的計画法を用いて最長共通部分列の長さを求めるアルゴリズム〕において、(1)文字が一致する場合に対応します。文字が一致するときは、 LCSL(n, k) = LCSL(n-1, k-1) + 1 が成り立つため、lcsl[n, k]に代入すべき式はlcsl[n-1, k-1] + 1です(左上マスの値+1)。

=lcsl[n-1,k-1] + 1

について〕
行17・19は(2)文字が一致しない場合に対応します。文字が一致しないときは、LCSL(n, k-1) と LCSL(n-1, k) の値を比較して、大きいほうの値を LCSL(n, k) の値とします。
行17は LCSL(n, k-1)>LCSL(n-1, k) であるときの処理なので、lcsl[n, k]に代入すべき式はlcsl[n, k-1]となります(左マスの値)。行19は反対に LCSL(n, k-1)<LCSL(n-1, k) であるときの処理なので、lcsl[n-1, k]が入ります(上マスの値)。

=lcsl[n,k-1]
 =lcsl[n-1,k]

について〕
行23は配列 icsl を全て埋めた後に関数の戻り値を返す処理に当たります。この関数の目的は、配列Sと配列Tの最長共通部分列の長さ LCSL(n,k) を得ることです。図3中では、右下のマス(7, 6)にこの値が求められます。この配列要素を参照するには、配列Sの要素数は変数s、配列Tの要素数は変数tを使い、lcsl[s, t]とします。

=lcsl[s, t]

設問4

本文中のに入れる適切な字句を,sとtを用いて答えよ。

解答例・解答の要点

キ:st

解説

について〕
O(オーダー)記法は、入力データ量が増えたときに、アルゴリズムの計算量やメモリ使用量がどの程度増加するかという観点でアルゴリズムを評価する考え方です。

プログラムの部分ごとに計算量を考えていきます。
行6~8
0からsまでのループなので実行回数は(s+1)回、計算量はO(s)です
行9~11
0からtまでのループなので実行回数は(t+1)回、計算量はO(t)です
行12~22
1からsまでのループ、1からtまでのループの二重ループ構造となっています。外側のループはs回実行され、その1回ごとに内側のループがt回実行されるため、実行回数は(s×t)回、計算量はO(st)です
行23
return文だけなので実行回数は1回、計算量はO(1)です
以上より、プログラム全体の計算量は O(s)+O(t)+O(st)+O(1) となります。オーダー記法では、データ量の増加に伴う計算量の増加傾向を簡潔に示すため、最も次数の高い項のみを残し、係数や低次の項、定数項は省略します。最も次数の大きいstのみを残し、その他の項は省略すると、プログラム全体の計算量は O(st) と表せます。したがって、空欄キには「st」が入ります。

=st
模範解答

Pagetop