CodeIQ:回文に分ける

私自身が表題の問題を解いた時のプログラムについて解説します。
問題の詳細は「回文に分ける」(CodeIQ)を参照してください。

問題の概要

問題を引用します。
【概要】
文字列を、二文字以上の回文の集まりに分ける方法が、何通りあるのかを数えてください。


【入出力】
入力は
aaabcbaaaa
のようになっています。
アルファベットの小文字だけの文字列です。

出力するのは、分ける方法が何通りあるかです。
先ほどの入力の場合は
aa/abcba/aaa
aaa/bcb/aaaa
aaa/bcb/aa/aa
のように、3 通りあるので、
aaabcbaaaa
に対応する出力は
3
になります。

【例】
入力 出力 分け方
aaabcbaaaa 3 aaa/bcb/aa/aa
aaa/bcb/aaaa
aa/abcba/aaa
ababcdcbaazzaa 2 aba/bcdcb/aa/zz/aa
aba/bcdcb/aazzaa
abcdedcba 1 abcdedcba

【補足】
不正な入力に対処する必要はありません。
文字列の長さは 1 文字以上で、 50 文字を超えることはありません。
全体をそのまま使うという分け方もひとつに数えます。

私のプログラム

Rubyで解答しています。

#!/usr/bin/ruby

$Memo = {}

def isKaibun(line)
	for i in 0...line.size
		j = line.size - i - 1
		break if i > j
		return false if line[i] != line[j]
	end
	return true
end

def solve(line)
	return $Memo[line] if $Memo[line] != nil
	return 0 if line.size < 2

	ret = isKaibun(line) ? 1 : 0
	for i in 1...line.size
		l = line[0..i]
		r = line[i+1..-1]

#		printf("%s, %s, %s, %s\n", l, r, isKaibun(l).to_s, isKaibun(r).to_s)
		next if !isKaibun(l)

		ret += solve(r)
	end

	$Memo[line] = ret
	return ret
end

# main
while line = gets
	line.strip!
	next if line.empty?

	p solve(line)
end

解説

わかりやすい問題と思います。
この出題者の問題としては珍しいタイプかもしれません。

考え方

単純に考えればOKです。

まず、入力値の先頭から2,3,4,…の文字数だけ切り出します。
それが回文で無いなら無視し、回文なら残りの文字列に対して同じ操作を繰り返します。
基本的にこの操作で文字列を回文に分割できます。

ただし、"aaaa"の様な文字列は"aa"、"aaa"、"aaaa"が回文になるので、回文を切り出せたらそれ以降の文字数の切り出しをやめるのではなく、文字列の長さ全てに対して操作する必要があります。

あとはメモ化すれば高速に処理できます。

main

入力値をsolve()に渡し、結果を印字します。
$Memoはある文字列の分解を行った結果を記録しておく領域です。

solve()

考え方に書いた処理を行います。

$Memoに引数の文字列の結果があれば操作ずみなので$Memoの結果を返します。
引数lineの長さが2未満なら回文ではなくなるので0を返します。

line自体が回文かをチェックし、回文なら返却値retを1、そうでなければ0にします。
lineから2,3,4,…,文字列の長さだけ先頭から文字列を切り出します。
lは切り出した文字列、rはその残りです。
lが回文で無いならうまく分けられていないので無視します。
solve()の引数にrを渡して再帰的に処理し、返却値を加算します。

$Memoにその時の引数lineをキー、retを値として記録します。
retを返却します。

isKaibun(line)

lineが回文かどうかをチェックします。
文字列の両端から中央に向かって文字が同じかをチェックします。
どこかで違っていたら回文では無いのでfalseを返却します。
iは先頭からの位置(インデックス番号)、jは末尾からの位置(インデックス番号)なのでi>jになったら真ん中を超えたので処理をやめます。
最後まで違いがなければ回文なのでtrueを返します。

雑感

問題を読んだ瞬間に、メモ化再帰で普通にできるなぁ、と思いました。
少し考えたのがコードの18行目の所で、回文を見つけたら1をreturnだとその文字列をさらに複数の回文にできる場合にうまく行かないのでreturnせずに処理する所くらいでしょうか。と言っても大した問題では無く、シンプルな問題だと思いました。