質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.48%
Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

Q&A

3回答

270閲覧

リスト探索の最小化について

babbleman

総合スコア107

Python

Pythonは、コードの読みやすさが特徴的なプログラミング言語の1つです。 強い型付け、動的型付けに対応しており、後方互換性がないバージョン2系とバージョン3系が使用されています。 商用製品の開発にも無料で使用でき、OSだけでなく仮想環境にも対応。Unicodeによる文字列操作をサポートしているため、日本語処理も標準で可能です。

1グッド

2クリップ

投稿2019/03/10 08:20

こんにちは。

1~nまでの数字がランダムに格納されているリストがあったとします。
そのリストの中には1~nの数字が少なくとも一つは必ずある場合、1~nを含む部分的なリストの長さの最小値を求めたいのですが
このような時はどのような考え方をすればよろしいのでしょうか?

私が考えている案としましては、まずは長さnで探索して、探索されたものを別のリスト(A)に格納する

そして長さnで見つからなかった場合は、Aの各要素の一番末尾に、次の要素の一番最後を追加する、というやり方でやればとても効率的な探索ができると思うのですがどうでしょうか?

そして、各要素に何の数字が足りないかを格納するリストを別に作っておけば、探索もとても楽になると思います。

そうなれば計算量も減ると思うのですが、皆さんはどうお考えですか?

皆様ならどうするかを教えてください、お願いいたします。

Lhankor_Mhy👍を押しています

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

hayataka2049

2019/03/10 10:23

問題の説明がよくわからないので、もう少し厳密に定義した上で(リストの長さは? 1~nを含む部分的なリストとは何を意味している?)具体例など示して説明していただけるとわかりやすくなるかと思います。
babbleman

2019/03/10 11:03

すみません、わかりやすく定義します。 とあるリストがあるとします。そこにはランダムな数字が格納されています。 例えば下のようなリストです。(n=5) [1,4,3,2,1,1,5,2,4] 1から5までの数字をすべて含むもので長さが最小のものはこのリストAのA[1:7]の6ですよね? これを求めたいです。
hayataka2049

2019/03/10 11:46 編集

修正依頼欄へのコメントでもいいですが、できれば質問文の編集で対応してください。
hayataka2049

2019/03/10 11:46

あとはアルゴリズムとかのタグを付けておくと良いかと。
hayataka2049

2019/03/10 12:34 編集

ついでに、今思いついている方法を実装したexecutableなpythonコードを載せていただけるとコミュニケーションのすれ違いが生じづらいと思います。
guest

回答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
torisan

総合スコア678

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Lhankor_Mhy

2019/03/15 10:48

123455551115555555555 とかだと、上手く行かなさそうな気がします。
torisan

2019/03/17 23:49

①の時点で条件を満たしていると思います。
babbleman

2019/03/30 05:13

少し気になったのが④→⑤のスライドを行う時に、例えばこの問題でいうと13桁目の「5」を先頭にして探索を行うなどの事例は考えなくても大丈夫なのでしょうか?
torisan

2019/03/31 23:40 編集

はい考慮しなくでも大丈夫です、2が見つかった位置で問題ありません。 仮に、後ろに2が存在しない場合はそのまま検索終了です。
退会済みユーザー

退会済みユーザー

2019/03/31 23:54

より短いものを探すという条件を満たさないから。素晴らしい。拍手。
guest

0

分析

この課題の規則を考えました。規則が多い方が効率的な探索ができると思うからです。

問題の性質(規則)

  • 1-nまでの数は必ずしも連続していなくてよい。
  • 最小の部分リストには1-nまでの数字が最低1回出現する。

最小の部分リストは最も短く、1-nまでの数字の出現度数の合計が最小。

  • 最小の部分リストの両端の数字の出現度数はそれぞれ1である。

もしも出現度数が1より大きればその端点を削除してより短くすることができる。

  • 探索開始時に左右両端で同じ数字が連続する場合は、同一の数字を内側に向かって削除して1つにまとめることができる。同型な文字列。

11111111111112334556543212344566666
1233455654321234456 <-- 同値変形

解の候補となる部分リスト

  • 12334556 左端の部分リスト
  • 654321 中央の部分リスト(最小)
  • 1234456 右端の部分リスト

一回のスキャンで下記のインデックスの分布表を作成することができます。この表から解法をみつけようとします。

数字インデックスの分布出現度数
10,122
21,11,133
32,3,10,144
44,9,15,164
55,6,8,174
67,182

アルゴリズム

右端と左端と中央で最小リストを発見します。左右は簡単に見つかりますが、中央は見つけにくいです。

探索

  • 左右両端に同じ数字が連続する場合は、同一の数字を内側に向かって削除して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

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

babbleman

2019/03/30 04:59

凄くわかりやすい回答をありがとうございます!今一度じっくり読ませていただきます。
guest

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
Lhankor_Mhy

総合スコア36115

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

Lhankor_Mhy

2019/03/15 10:36

N>>nなら、ユニークチェックのコストにもよるけど、ご質問者のアルゴリズムの方が効率的かもですね。
Lhankor_Mhy

2019/03/15 10:37

あ、違う、これ、ダメか。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

まだベストアンサーが選ばれていません

会員登録して回答してみよう

アカウントをお持ちの方は

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.48%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問