こんにちは。
1~nまでの数字がランダムに格納されているリストがあったとします。
そのリストの中には1~nの数字が少なくとも一つは必ずある場合、1~nを含む部分的なリストの長さの最小値を求めたいのですが
このような時はどのような考え方をすればよろしいのでしょうか?
私が考えている案としましては、まずは長さnで探索して、探索されたものを別のリスト(A)に格納する
そして長さnで見つからなかった場合は、Aの各要素の一番末尾に、次の要素の一番最後を追加する、というやり方でやればとても効率的な探索ができると思うのですがどうでしょうか?
そして、各要素に何の数字が足りないかを格納するリストを別に作っておけば、探索もとても楽になると思います。
そうなれば計算量も減ると思うのですが、皆さんはどうお考えですか?
皆様ならどうするかを教えてください、お願いいたします。
気になる質問をクリップする
クリップした質問は、後からいつでもMYページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/03/10 11:45 編集
2019/03/10 11:46 編集
2019/03/10 11:46
2019/03/10 12:34 編集

回答3件
0
最初に基準を決め、右にスライドしつつ頭を削っていく方法です。
最初に基準を決める。後は1.2.または3.を繰り返します。
1. 一番左の数字が重複する場合は1桁を残して削除
2-1.一番左の数字が、一番左の数字以外の検索範囲に存在する場合は削除
2-2.一番左の数字が、一番左の数字以外の検索範囲に存在しない場合は、桁数を保持しつつスライド
3.検索範囲にn個の数字が含まれない場合も、桁数を保持しつつスライド
わかりにくいので以下、例。
①1桁目から全ての数字が含まれるまでの桁数を調べる。最小桁数を更新する。
②一番左の数字が2桁目以降と被っている時は、1桁分を残して検索範囲から切り捨てる。最小桁数を更新する。
③一番左の数字が一番左の数字以外の検索範囲の中に含まれる場合、一番左の数字を検索範囲から切り捨てる。最小桁数を更新する。
④②と同じ。
⑤③と同じだが、一番左の数字が一番左の数字以外の検索範囲の中に含まれていない。検索範囲の右側から一番左の数字を探していく。
そして、見つけた箇所から左に最小桁数分を検索範囲とする。
⑥全ての数字が含まれない為、桁数を維持しスライドする。
⑦⑥と同じ。
⑧一番左の数字が2桁目以降と被っていないので、②で行った切捨てはなし。③と同じく一番左の数字が検索範囲の中に含まれているので、一番左の数字を検索範囲から切り捨てる。最小桁数を更新する。
⑨一番左の数字が2桁目以降と被っていないので、②で行った切捨てはなし。⑤と同じく一番左の数字が検索範囲の中に含まれていない。検索範囲の右側から一番左の数字を探すが、存在しないため処理終了。
抜けがある場合ご指摘ください。
<<<追記>>>
⑥⑦のスライド部分は、⑤のように
見つからない数字を探し、その桁から左側に最小桁数分を取る、
のほうが早いですね。
(例:最小桁数7桁とする)
(1)1~7桁目に5がないので、8桁目より右側の5を探す。
1234123<14214135142531221 : (1~7桁)
(2)15桁目にあるので、右端を15桁目として7桁分取る
12341231>4114135<142531221 : (9~15桁)
9~15桁目に2がないので、16桁目より右側の2を探す。
(3)18桁目にあるので、右端を18桁目として7桁分取る
12341231411>4135142<531221 : (12~18桁)
全てあるので検索再開。
投稿2019/03/15 05:18
編集2019/03/19 01:03総合スコア678
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/03/15 10:48

退会済みユーザー
2019/03/31 23:54

0
分析
この課題の規則を考えました。規則が多い方が効率的な探索ができると思うからです。
問題の性質(規則)
- 1-nまでの数は必ずしも連続していなくてよい。
- 最小の部分リストには1-nまでの数字が最低1回出現する。
最小の部分リストは最も短く、1-nまでの数字の出現度数の合計が最小。
- 最小の部分リストの両端の数字の出現度数はそれぞれ1である。
もしも出現度数が1より大きればその端点を削除してより短くすることができる。
- 探索開始時に左右両端で同じ数字が連続する場合は、同一の数字を内側に向かって削除して1つにまとめることができる。同型な文字列。
例
11111111111112334556543212344566666
1233455654321234456 <-- 同値変形
解の候補となる部分リスト
- 12334556 左端の部分リスト
- 654321 中央の部分リスト(最小)
- 1234456 右端の部分リスト
一回のスキャンで下記のインデックスの分布表を作成することができます。この表から解法をみつけようとします。
数字 | インデックスの分布 | 出現度数 |
---|---|---|
1 | 0,12 | 2 |
2 | 1,11,13 | 3 |
3 | 2,3,10,14 | 4 |
4 | 4,9,15,16 | 4 |
5 | 5,6,8,17 | 4 |
6 | 7,18 | 2 |
アルゴリズム
右端と左端と中央で最小リストを発見します。左右は簡単に見つかりますが、中央は見つけにくいです。
探索
- 左右両端に同じ数字が連続する場合は、同一の数字を内側に向かって削除して1つにまとめる。
- もっとも左側の部分リスト (1) min(分布の左端のインデックス) 〜 (2) max(分布の左端のインデックス)
(2)を固定、(1)から右側へ出現度数が1になる数字に出会うまで端点を削除する。<-- 実際は行わない
- もっとも右側の部分リスト (1) min(分布の右端のインデックス) 〜 (2) max(分布の右端のインデックス)
(1)を固定、(2)から左側へ出現度数が1になる数字に出会うまで端点を削除する。<-- 実際は行わない
判断
- もっとも左側の部分リストともっとも右側の部分リストが一致または端点を共有するなら、短いほうを採用して探索終了。この部分リストを(A)とする。
- もっとも左側の部分リストともっとも右側の部分リストが一致しないなら、両方(短いほう?)に再帰的な探索処理を実行する。
追加の探索
- 中央の部分リスト
もっとも左側の部分リストの左端点と、もっとも右側の部分リストの右端点を除き、内側の数列に再帰的な探索処理を実行する。左端点も右端点も出現度数が1で削除できない場合は、再帰探索しない。得られた部分リストを(B)とする。
結果
- 最小の部分リスト
(B)と左右の(A)と比較して短いほうが求める解。
自己評価
- できれば、インデックスの分布表を一度だけ作成し、それを複製、変形することで解を得たい。
数列が短ければ効率が悪いかもしれないが、長くなれば効率がよくなるのではなかろうか。- 再帰関数を参照透過にすれば、部分解を得るのに並行処理などが可能になるだろう。
簡単な動作確認を行いましたが詳細まで詰めきれていません。問題点をご指摘ください。
結論 (2019-03-21)
数字が未知であり、もっとも右端、もっとも左端だけの探索なら早いのですが、中央の部分リストの効率的な探索方法を見つけられませんでした。再帰呼出しの数は文字列の長さに比例して増えてしまいます。未熟なアイデアでした。
ビットを数える解 (2019-03-22)
敗北の証として、ビット 1 を数えることで数が揃っていることを判定するプログラムを書きました。概要は以下のとおり。
- 1-N の数字は未知である。数字は連続していなくてもよいが、1 -32 までしか許さない。
- 前処理で、数字 n を 32 ビットのパターンに置き換える。数字ごとにワードの n-1 番目のビットを 1 にする。
- 前処理で、使われるすべての数字のビットパターンの論理和と個数 K を調べる。
- 調査対象の数列のビットパターンの論理和をとり、ビットの個数が K なら数字が揃っていると判断する。
- 直近の最短リストのインデックスを持たせる。最短インデックスが更新される都度、探索の長さが短くなる。
- 先に同じ数字が出現した場合、排他的論理和を使って無視できるか判定する。(不完全ですが ... )
ビットの個数を数える方法は、以下の「バージョン5」を引用しました。
ビットを数える・探すアルゴリズム
python3
1import functools 2def findMinList(source): 3 4 def bitpattern(n): # n-1番目のビットをon 5 return 1 << (n - 1) 6 7 def numlist(lst): # 数列をビットパターンのリストに変換 8 return list(map(bitpattern,lst)) 9 10 def bitmask(m): # すべての数の論理和 11 return functools.reduce(lambda x, y: x | y, m) 12 13 def numofbits5(bits): # 1のビットの数を数える http://www.nminoru.jp/~nminoru/programming/bitcount.html 14 bits = (bits & 0x55555555) + (bits >> 1 & 0x55555555) 15 bits = (bits & 0x33333333) + (bits >> 2 & 0x33333333) 16 bits = (bits & 0x0f0f0f0f) + (bits >> 4 & 0x0f0f0f0f) 17 bits = (bits & 0x00ff00ff) + (bits >> 8 & 0x00ff00ff) 18 return (bits & 0x0000ffff) + (bits >>16 & 0x0000ffff) 19 20 #print(source) 21 length = len(source) # リストの長さ 22 mlist = numlist(source) # ビットパターンのリスト 23 ncount = numofbits5(bitmask(mlist)) # 数の種類(n 個) 24 (s,e) = (0,length) # 直近の結果のインデックス(最短リスト) 25 i = 0 26 while ((e-s+1) != ncount) and (i < length - ncount + 1): 27 mm = 0 28 for j in range(i, min(length, i+e-s)): # 最短リスト - 1 の長さしか調べない 29 i = j if mm ^ mlist[j] == 0 else i # 先行する数字を無視できるか? 30 mm |= mlist[j] # 累積ビットパターン 31 if numofbits5(mm) == ncount: # n 個揃った 32 #print("+:{0},{1} = {2}".format(i,j,j-i+1)) 33 s,e = (i,j) if (j-i) < (e-s) else (s,e) 34 break 35 #else: 36 #print("-:{0},{1}".format(i,j)) 37 i += 1 38 return (s,e)
探索回数を減らす
改善案として、前処理で左端と右端の重複する数字の無視をあげておきます。
python3
1 def trimleft(ilist): # 左端の重複を排除 2 s = ilist[0] 3 for i in range(len(ilist)): 4 if s != ilist[i]: 5 break 6 return max(0,i-1) 7 8 def trimright(ilist): # 右端の重複を排除 9 e, leng = ilist[-1], len(ilist) 10 for i in range(-1, (-1) - leng, -1): 11 if e != ilist[i]: 12 break 13 return min(leng+i+1,leng-1)
修正点 (2019-03-24)
- k を削除する。i を直接変更することで、探索失敗後の探索を効率化。
- 「最短リストの長さ」しか調べないを「最短リストの長さ - 1」に変更。
- 「右端の重複を排除」の変数を修正。
- 例を追加。
python3
1def findShortest(numstr): 2 return findMinList(list(map(int, numstr))) 3 4print(findShortest("11222314113351413141121511")) 5print(findShortest("12223141133514131411215111111111111")) 6print(findShortest("11222314113351413141144444444444444421511")) 7print(findShortest("2314113351413123141133511")) 8print(findShortest("1233455654321234456")) 9print(findShortest("123455551115555555555")) 10print(findShortest("33123456521223456")) 11print(findShortest("123345561234456"))
修正点 (2019-03-25)
- numlist のラムダ式が冗長なので削除。
- 直近の結果のインデックス(最短リスト)の初期値を length-1 から length に変更。
- i の上限に +1
- while 条件を変更。一度もループしない場合があります。
- ソースコードをすべて最新に置き換え。
あれこれ考えた結果、バグが多なりました。申し訳ありません。なにかのお役に立てれば。
投稿2019/03/18 11:24
編集2019/03/24 17:00
退会済みユーザー
総合スコア0
0
すみません、これ、ダメですね。
末尾からひとつつづ除去していって、それ以上取れなくなったら先頭から同じことをする、というクソ真面目なアルゴリズムが一番効率がよさそうな気がします。
python
1import random 2n = 4 3 4N = 50 5L = [random.randint( 0, n - 1 ) for i in range( N ) ] 6 7cloneL = L[:] 8itemsNum = [ cloneL.count(x) for x in range( 0, n ) ] 9 10for i in range( N ): 11 cuttedItem = cloneL.pop() 12 itemsNum[cuttedItem] -= 1 13 print(itemsNum) 14 if min( itemsNum ) == 0: 15 itemsNum[cuttedItem] += 1 16 cloneL.append( cuttedItem ) 17 break 18 19for i in range( N ): 20 cuttedItem = cloneL.pop(0) 21 itemsNum[cuttedItem] -= 1 22 print(itemsNum) 23 if min( itemsNum ) == 0: 24 itemsNum[cuttedItem] += 1 25 cloneL.insert( 0, cuttedItem ) 26 break 27 28print( len(cloneL), cloneL, L )
おそらく、キモは、いちいちユニークチェックをしなくていいように、最初にリストをなめて要素ごとのカウンタを別に持っておくことではないかと。
投稿2019/03/15 10:31
編集2019/03/15 10:39総合スコア37438
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

あなたの回答
tips
太字
斜体
打ち消し線
見出し
引用テキストの挿入
コードの挿入
リンクの挿入
リストの挿入
番号リストの挿入
表の挿入
水平線の挿入
プレビュー
質問の解決につながる回答をしましょう。 サンプルコードなど、より具体的な説明があると質問者の理解の助けになります。 また、読む側のことを考えた、分かりやすい文章を心がけましょう。