問題
- 1, 2, ..., 99が並び替えられた数列$s$が以下のような形式で与えられたとき,コンマ区切りで数列を出力せよ.
- 例
- 入力
194844479992885193431281672755538710981664457433843176692585632179368224916037252154184211658086419457126285078707285638157343020599675354922614657775295148733139079401662636483232968398997
- 期待される出力
19,48,44,47,99,92,88,51,93,43,12,81,67,27,55,53,87,10,98,1,66,4,45,74,33,84,31,76,69,2,58,56,32,17,9,36,82,24,91,60,37,25,21,54,18,42,11,65,80,86,41,94,5,71,26,28,50,78,70,72,85,6,38,15,7,34,30,20,59,96,75,35,49,22,61,46,57,77,52,95,14,8,73,3,13,90,79,40,16,62,63,64,83,23,29,68,39,89,97
- 入力
ここから先は筆者の考え方を示すので,解き方を考える方は一回スクロールしないほうがいいかもしれません
考え方(失敗バージョン)
- 数字を1つ進める場合と2つ進める場合がある
1つ進めるのは1桁の数字(1-9)のときなので,これらの9個の数字の場所を全探索できれば解けそう...!- 各数字の存在箇所は20で,$20^9$ 回の計算となり回りませんでした...
- 計算量を減らしていく必要がありそう
考え方(うまくいったバージョン)
- 例えば10, 20などの一の位が0の2桁の数字は,数列の中で1か所しか存在しない
- このように数列のなかで1回しか出てこないような2桁の数字を検出して,消していけばよさそう
- 具体的な手順を以下に示します
-
1, 2, ..., 99が検出されたかをメモする配列checkedを用意
checked = [False]*99 # Trueなら検出済み
-
文字列sから数字のリストnumに変換する
s = "194844479992885193431281672755538710981664457433843176692585632179368224916037252154184211658086419457126285078707285638157343020599675354922614657775295148733139079401662636483232968398997" num = [int(s[i]) for i in range(len(s))]
-
一の位が0の2桁の数を消し,部分列に分割
for i in range(10,100,10): checked[i-1] = True num.append(1) num.append(0) sequence = [] # 分割後の数列を格納 lower = 0 for i in range(len(num)): if num[i] == 0: sequence.append(num[lower:i-1]) lower = i+1 num.pop(-1) num.pop(-1) print(sequence)
- 出力
[[1, 9, 4, 8, 4, 4, 4, 7, 9, 9, 9, 2, 8, 8, 5, 1, 9, 3, 4, 3, 1, 2, 8, 1, 6, 7, 2, 7, 5, 5, 5, 3, 8, 7], [9, 8, 1, 6, 6, 4, 4, 5, 7, 4, 3, 3, 8, 4, 3, 1, 7, 6, 6, 9, 2, 5, 8, 5, 6, 3, 2, 1, 7, 9, 3, 6, 8, 2, 2, 4, 9, 1], [3, 7, 2, 5, 2, 1, 5, 4, 1, 8, 4, 2, 1, 1, 6, 5], [8, 6, 4, 1, 9, 4, 5, 7, 1, 2, 6, 2, 8], [7, 8], [7, 2, 8, 5, 6, 3, 8, 1, 5, 7, 3, 4], [], [5, 9, 9, 6, 7, 5, 3, 5, 4, 9, 2, 2, 6, 1, 4, 6, 5, 7, 7, 7, 5, 2, 9, 5, 1, 4, 8, 7, 3, 3, 1, 3], [7, 9], [1, 6, 6, 2, 6, 3, 6, 4, 8, 3, 2, 3, 2, 9, 6, 8, 3, 9, 8, 9, 9, 7]]
検出した数字を切れ目として複数の部分列に分割する
出力の ] と [ の間にはもともと2桁の数字が存在していた -
それ以外の2桁の数字をふるい落とす
sequence_sieved = two_digit_sieve(sequence) print(sequence_sieved)
2桁の数字をふるい落とす関数two_digit_sieveの説明は次章で行います
-
出力
sequence_sieved: [[], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [1], [4], [], [], [], [], [], [], [2], [], [], [], [9], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [5], [], [], [], [], [], [], [], [6], [], [7], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [], [8], [3], [], [], [], [], [], [], [], [], [], [], [], [], [], [], []]
2桁の数字はすべてふるい落とされる
-
-
ふるい落とされた数列で,] と [ の間にはもともと2桁の数があったはず
-
] [ を読むたびにindexを1つ増やしながら,1桁の数がもともとどこにあったか計算
one_digit_idx = [] count = 0 flag = 0 for i in range(len(sequence_sieved)): for j in range(len(sequence_sieved[i])): one_digit_idx.append(count) # indexを保存 count += 1 # 1桁の数字の分進める count += 2 # ][の分進める print("one_digit_idx:",one_digit_idx)
-
出力
one_digit_idx: [38, 41, 56, 65, 100, 117, 122, 155, 158]
-
-
得られたindexをもとにしてもとの数列の復元を行う
ans = [] i = 0 # 何桁進めるか制御 while(True): if i in one_digit_idx: ans.append(num[i]) i += 1 else: if i < len(num)-1: ans.append(num[i]*10+num[i+1]) i += 2 if i >= len(num)-1: break print("\n===ans===\n", ans)
-
出力
===ans=== [19, 48, 44, 47, 99, 92, 88, 51, 93, 43, 12, 81, 67, 27, 55, 53, 87, 10, 98, 1, 66, 4, 45, 74, 33, 84, 31, 76, 69, 2, 58, 56, 32, 17, 9, 36, 82, 24, 91, 60, 37, 25, 21, 54, 18, 42, 11, 65, 80, 86, 41, 94, 5, 71, 26, 28, 50, 78, 70, 72, 85, 6, 38, 15, 7, 34, 30, 20, 59, 96, 75, 35, 49, 22, 61, 46, 57, 77, 52, 95, 14, 8, 73, 3, 13, 90, 79, 40, 16, 62, 63, 64, 83, 23, 29, 68, 39, 89, 97]
-
-
数分解があっているかをセルフで判定
ideal_answer = [i for i in range(1,100)] judge = True # 1-99が1つずつ含まれているか判定 for i in ans: if i in ideal_answer: ideal_answer.remove(i) else: print(i,"is duplicated") if len(ideal_answer) != 0 or len(ans) != 99: judge = False # ansの結合が元の文字列と同じであるか判定 if ''.join(map(str, ans)) != s: judge = False if judge: print("\nanswer is True!!") else: print("\nanswer is False")
-
出力
answer is True!!
-
2桁の数をふるい落とすアルゴリズム
2桁の整数をふるい落としきれない(複数パターンの解が存在する)際のアルゴリズムに誤りがありました。時間があるときに編集します(2024/06/01)。
-
two_digit_sieve の内容について説明
import copy def two_digit_sieve(sequence): two_digit = [] for i in range(11,100): if i % 10 != 0: two_digit.append(i) false_num_before = checked.count(False) while(checked.count(False) >= 10): two_digit_count = [0 for i in range(10,100)] two_digit_dict = dict(zip(two_digit, two_digit_count)) # 2桁の数字の登場回数を数える for i in range(len(sequence)): for j in range(len(sequence[i])-1): two_digit_dict[sequence[i][j]*10+sequence[i][j+1]] += 1 # 削除する2けたの数字を記憶するリスト remove_list = [] # 登場回数が1回の場合,削除リストに入れる for td in two_digit: if two_digit_dict[td] == 1: checked[td-1] = True remove_list.append(td) # 削除リストに従って,部分列をさらに分断 sequence_trimmed = [] for i in range(len(sequence)): lower = 0 j = 0 while(j < len(sequence[i])-1): if sequence[i][j]*10 + sequence[i][j+1] in remove_list: sequence_trimmed.append(sequence[i][lower:j]) lower = j+2 remove_list.remove(sequence[i][j]*10 + sequence[i][j+1]) j += 1 j += 1 sequence_trimmed.append(sequence[i][lower:]) sequence = copy.deepcopy(sequence_trimmed) print("the number of undetected:",checked.count(False)) print("sequence:",sequence) print("the length of sequence:",len(sequence)) print() # 無限ループ対策 if checked.count(False) == false_num_before: break false_num_before = checked.count(False) # ふるい落とした結果,決めきれない2桁の数字が残った場合の処理 if checked.count(False) > 9: print("sequence_undecided:",sequence) sequence_decided = [] remove_list = [] for i in range(len(sequence)): if len(sequence[i]) > 1: for j in range(len(sequence[i])-1): # 決めきれていない2桁の数字を強制的に決める if checked[sequence[i][j]*10 + sequence[i][j+1]-1] == False: remove_list.append(sequence[i][j]*10 + sequence[i][j+1]) checked[sequence[i][j]*10 + sequence[i][j+1]-1] = True for i in range(len(sequence)): lower = 0 j = 0 while(j < len(sequence[i])-1): if sequence[i][j]*10 + sequence[i][j+1] in remove_list: sequence_decided.append(sequence[i][lower:j]) lower = j+2 remove_list.remove(sequence[i][j]*10 + sequence[i][j+1]) j += 2 else: j += 1 sequence_decided.append(sequence[i][lower:]) sequence = copy.deepcopy(sequence_decided) return sequence
関数の計算量
- whileループは1回回るごとに最低1つ,2桁の整数がふるい落とされるので最悪90回程度
- whileループ内処理は最悪の場合numのサイズの2乗で40000程度
- 全体で90×40000 < $10^7$なので計算できそう
- アルゴリズム初心者なので,よりよい解き方があればご教示ください!
- これから勉強して色々書いてみます