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

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

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

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

Python

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

Q&A

解決済

2回答

329閲覧

配列で複数の整数が与えられた時に和が最も大きくなる範囲とその和を求めるアルゴリズムについて

退会済みユーザー

退会済みユーザー

総合スコア0

Python 3.x

Python 3はPythonプログラミング言語の最新バージョンであり、2008年12月3日にリリースされました。

アルゴリズム

アルゴリズムとは、定められた目的を達成するために、プログラムの理論的な動作を定義するものです。

Python

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

0グッド

1クリップ

投稿2019/05/29 08:28

前提・実現したいこと

配列で複数の整数が与えられた時に和が最も大きくなる範囲とその和を求めるプログラムを書いています。

例えば

A = [18, -10, 30, 23, -26, 37, 48, -19, -13, 31] なら 112, 2, 6

発生している問題・エラーメッセージ

出力は出ていて、for文も回っているようなので何が問題なのか修正方法がわからず困っています。

現行のコードの出力

(119, 0, 0)

{30, 23, -26, 37, 48}の範囲で i = 2, j = 6なので、出力としては以下が正しいと考えられます。

112, 2, 6

該当のソースコード

python

1def solution(A): 2 start = 0 3 end = len(A) 4 max_total = 0 5 tmp = 0 6 for i in range(len(A)): 7 for j in range(len(A)-1, i-1, -1): 8 tmp += A[j] 9 if max_total < tmp: 10 max_total = tmp 11 start = i 12 end = j 13 tmp = 0 14 return max_total, start, end 15 16if __name__ == '__main__': 17 A = [18, -10, 30, 23, -26, 37, 48, -19, -13, 31] 18 ans = solution(A) 19 print(ans)

補足情報(FW/ツールのバージョンなど)

Python 3.6

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

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

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

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

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

torisan

2019/05/29 08:33

結果が119になるに当たって何を足しているか、追っていってみてはどうでしょうか? 想定より大きいという事は、明らかに無駄な部分があるという事ですし。
tiitoi

2019/05/29 08:45

A[0:7] = {18, -10, 30, 23, -26, 37, 48} の 120 が最大ではないでしょうか?
otolab

2019/05/29 10:29

(完全に余談) あ、これO(n)で解けるって言われませんでした?この問題、懐かしい。
退会済みユーザー

退会済みユーザー

2019/05/29 12:37

ご指摘とコメントをありがとうございます。 O(n)で解けるとはどういうことでしょうか。現行のプログラムだと55回探索をしていますが、n+(n-1)+(n-2)+....+2+1で結局オーダ記法にするとO(n)ということでしょうか。
退会済みユーザー

退会済みユーザー

2019/05/29 13:06

もしご存知でしたらこの問題の名称も教えていただけると幸いです。
jun68ykt

2019/05/29 13:45

otolabさんの余談に乗っかって、O(n)で解を求めるコードを追記しました。たぶん、追記のコードでうまくいくのではないかと思ってますが、おかしかったらご指摘ください。
otolab

2019/05/30 01:36

jun68ykt すごい早いw 自分も一応は解けたのですが、もっとややこしく考えてしまって時間がかかった覚えがあります。
guest

回答2

0

全探索でやる場合は以下のように範囲の始点 i に対して、i + 1 ~ 末尾までの終点の和を計算すればよいと思います。
和の計算は for 文を回すのではなく、組み込み関数 sum() を使いましょう。

python

1def solution(lst, verbose=False): 2 max_total = min(lst) - 1 # 必ず1回は if 文が実行される値を初期値としておく。 3 4 total = 0 5 for i in range(len(lst)): 6 for j in range(i + 1, len(lst) + 1): 7 total = sum(lst[i:j]) 8 if verbose: # デバッグ用 9 print(f"lst[{i}:{j}] = {lst[i:j]}, sum = {total}") 10 11 if max_total < total: 12 max_total = total 13 start = i 14 end = j 15 return max_total, start, end 16 17 18lst = [18, -10, 30, 23, -26, 37, 48, -19, -13, 31] 19print(solution(A, True))
(120, 0, 7)
lst[0:1] = [18], sum = 18 lst[0:2] = [18, -10], sum = 8 lst[0:3] = [18, -10, 30], sum = 38 lst[0:4] = [18, -10, 30, 23], sum = 61 lst[0:5] = [18, -10, 30, 23, -26], sum = 35 lst[0:6] = [18, -10, 30, 23, -26, 37], sum = 72 lst[0:7] = [18, -10, 30, 23, -26, 37, 48], sum = 120 lst[0:8] = [18, -10, 30, 23, -26, 37, 48, -19], sum = 101 lst[0:9] = [18, -10, 30, 23, -26, 37, 48, -19, -13], sum = 88 lst[0:10] = [18, -10, 30, 23, -26, 37, 48, -19, -13, 31], sum = 119 lst[1:2] = [-10], sum = -10 lst[1:3] = [-10, 30], sum = 20 lst[1:4] = [-10, 30, 23], sum = 43 lst[1:5] = [-10, 30, 23, -26], sum = 17 lst[1:6] = [-10, 30, 23, -26, 37], sum = 54 lst[1:7] = [-10, 30, 23, -26, 37, 48], sum = 102 lst[1:8] = [-10, 30, 23, -26, 37, 48, -19], sum = 83 lst[1:9] = [-10, 30, 23, -26, 37, 48, -19, -13], sum = 70 lst[1:10] = [-10, 30, 23, -26, 37, 48, -19, -13, 31], sum = 101 lst[2:3] = [30], sum = 30 lst[2:4] = [30, 23], sum = 53 lst[2:5] = [30, 23, -26], sum = 27 lst[2:6] = [30, 23, -26, 37], sum = 64 lst[2:7] = [30, 23, -26, 37, 48], sum = 112 lst[2:8] = [30, 23, -26, 37, 48, -19], sum = 93 lst[2:9] = [30, 23, -26, 37, 48, -19, -13], sum = 80 lst[2:10] = [30, 23, -26, 37, 48, -19, -13, 31], sum = 111 lst[3:4] = [23], sum = 23 lst[3:5] = [23, -26], sum = -3 lst[3:6] = [23, -26, 37], sum = 34 lst[3:7] = [23, -26, 37, 48], sum = 82 lst[3:8] = [23, -26, 37, 48, -19], sum = 63 lst[3:9] = [23, -26, 37, 48, -19, -13], sum = 50 lst[3:10] = [23, -26, 37, 48, -19, -13, 31], sum = 81 lst[4:5] = [-26], sum = -26 lst[4:6] = [-26, 37], sum = 11 lst[4:7] = [-26, 37, 48], sum = 59 lst[4:8] = [-26, 37, 48, -19], sum = 40 lst[4:9] = [-26, 37, 48, -19, -13], sum = 27 lst[4:10] = [-26, 37, 48, -19, -13, 31], sum = 58 lst[5:6] = [37], sum = 37 lst[5:7] = [37, 48], sum = 85 lst[5:8] = [37, 48, -19], sum = 66 lst[5:9] = [37, 48, -19, -13], sum = 53 lst[5:10] = [37, 48, -19, -13, 31], sum = 84 lst[6:7] = [48], sum = 48 lst[6:8] = [48, -19], sum = 29 lst[6:9] = [48, -19, -13], sum = 16 lst[6:10] = [48, -19, -13, 31], sum = 47 lst[7:8] = [-19], sum = -19 lst[7:9] = [-19, -13], sum = -32 lst[7:10] = [-19, -13, 31], sum = -1 lst[8:9] = [-13], sum = -13 lst[8:10] = [-13, 31], sum = 18 lst[9:10] = [31], sum = 31 (120, 0, 7)

投稿2019/05/29 08:53

tiitoi

総合スコア21956

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

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

退会済みユーザー

退会済みユーザー

2019/05/29 12:31

ご回答いただきましてありがとうございます。 今後デバグの際は紹介いただいたように表示させて確認しようと思います。 組み込み関数 sum() を使った方が良い理由は計算速度でしょうか。
tiitoi

2019/05/29 14:51

速度というより、コードの簡潔さですね。 C 言語であれば、質問のコードのように for で書きますが、Python は標準ライブラリが充実してるので、それを使って少ない行数でかけるなら利用したほうがよいかと思います。
guest

0

ベストアンサー

こんにちは

はじめに題意の確認ですが、ご質問に

A = [18, -10, 30, 23, -26, 37, 48, -19, -13, 31]

として、

{30, 23, -26, 37, 48}の範囲で i = 2, j = 6なので、出力としては以下が正しいと考えられます。

112, 2, 6

とあったので、(上記自体は最大ではありませんが、)欲しい出力の形式は

(合計値の最大値, 合計が最大になる部分リストの開始インデクス, 合計が最大になる部分配列の終端インデクス)

という要素が3つのタプルで、合計が最大になる部分リストの終端インデクス も、合計の対象に含まれる と解釈しました。以下、これを前提とした回答になります。

まず、ご質問に上げられているコードでは何が拙いかというと、合計の比較の際に、以下のようにしている箇所です。

python3

1for j in range(len(A)-1, i-1, -1): 2 tmp += A[j] 3 if max_total < tmp:

これだと、A の部分リストのうち、終端がAの終端と同じであるもの、すなわち 以下の10種類のリスト

  • [31]
  • [-13,31]
  • ・・・
  • [18, -10, 30, 23, -26, 37, 48, -19, -13, 31] (Aと同じ)

の要素の合計値を比較しています。
二重にループを回して、くまなく合計を比較しているように見えて、コードを追っていけば、比較対象となる合計値としては、上記の10種類のリストの合計値しか出てきません。

開始インデクスを i ( 0<=i<len(A) )とすると 終端インデクスの取り得る場合の数は len(A)-i 個 で 、いま len(A) は 10 なので、比較対象となる合計値は、重複を考慮しなければ(すなわち、異なる部分リストの要素の合計が(たまたま)一致してもそれらは個別に数えるとして、)

10+9+8+7+6+5+4+3+2+1 = 55

あるはずです。これを抑えたうえで、二重のループによって、合計を算出すべき部分リストが 55 種類出現しているかという観点で、見直されるとよいかと思います。

次に、具体的な修正案ですが、ご質問にあるコードをなるべく活かすとすると、たとえば以下のようになるかと思います。

python3

1def solution(A): 2 start = None 3 end = None 4 max_total = 0 5 6 for i in range(len(A)): 7 for j in range(i, len(A)): 8 tmp = sum(A[i:j+1]) 9 if max_total < tmp: 10 max_total = tmp 11 start = i 12 end = j 13 14 return max_total, start, end 15 16 17if __name__ == '__main__': 18 A = [18, -10, 30, 23, -26, 37, 48, -19, -13, 31] 19 ans = solution(A) 20 print(ans) 21

上記を実行すると

(120, 0, 6)

と表示されます。開始インデクス 0 、終端インデクス 6(※これも含む) であるAの部分リスト

[18, -10, 30, 23, -26, 37, 48]

の要素の合計値 120 が最大となることが分かります。
なお上記のコードでは、 A が空のリストであるとき、開始と終端が求められないことを表すために
(0, None, None)
を返すようにしています。

もうひとつ別解を以下に挙げます。

python3

1def solution(A): 2 if len(A) == 0: 3 return 0, None, None 4 5 totals = sorted( 6 [(sum(A[i:j+1]), i, j) 7 for i in range(len(A)) 8 for j in range(len(A)) 9 if i <= j 10 ], 11 key=lambda x: x[0], 12 reverse=True) 13 14 return totals[0] 15 16 17if __name__ == '__main__': 18 A = [18, -10, 30, 23, -26, 37, 48, -19, -13, 31] 19 ans = solution(A) 20 print(ans)

上記で、 sorted の第1引数になる、内包表記によるリスト

pyhon3

1[(sum(A[i:j+1]), i, j) 2 for i in range(len(A)) 3 for j in range(len(A)) 4 if i <= j 5]

は、以下のようなタプル

(部分リストの要素の合計値, 部分リストの開始インデクス, 部分リストの終端インデクス)

を、A の部分リストのすべてについて含み、このリストの長さは先に求めた、場合の数の 55 になります。

以上、参考になれば幸いです。

追記

すべての部分リストを調べなくても、以下で大丈夫かなと思います。

python3

1def solution(A): 2 start = None 3 end = None 4 5 if len(A) == 0: 6 return 0, start, end 7 8 # 開始インデクスを求める 9 start = 0 10 tmp = 0 11 for i in range(len(A)): 12 tmp += A[i] 13 if tmp <= 0 and i+1 < len(A): 14 start = i+1 15 16 # 終端インデクスを求める 17 tmp = 0 18 max_total = None 19 20 for j in range(start, len(A)): 21 tmp += A[j] 22 if max_total is None or tmp > max_total: 23 end = j 24 max_total = tmp 25 26 return max_total, start, end 27 28 29if __name__ == '__main__': 30 A = [-1, 5, -10, 9, -4, 18, -10, 30, 23, -26, 37, 48, -19, -13, 31] 31 ans = solution(A) 32 print(ans)

(120, 5, 11)

上記は二重のループがないので、 O(n) で済んでいます。

追記2

上記のコードが正解ではないことを、コメントからご指摘、ありがとうございます。
自分でやろうと思ったのですが、たぶんどこかに良質な回答のコードがあるだろうと思って調べましたところ、以下を見つけました。

If you want the start and end slice indices, too, you need to track a few more bits of information (note this is still O(1) space and O(n) time, it's just a bit hairier):

python

1def mssl(l): 2 best = cur = 0 3 curi = starti = besti = 0 4 for ind, i in enumerate(l): 5 if cur+i > 0: 6 cur += i 7 else: # reset start position 8 cur, curi = 0, ind+1 9 10 if cur > best: 11 starti, besti, best = curi, ind+1, cur 12 return starti, besti, best

上記に対して、

A = [-1, 5, -10, 9, -4, 18, -10, 30, 23, -26, 37, 48, -19, -13, 31]

を与えたサンプルを以下に作成しました。

上記を実行すると

(3, 12, 125)

と表示されます。このコードの場合、 (開始, 終端, 合計) を返しており、終端 は含まない形式で返してくれます。

投稿2019/05/29 12:11

編集2019/05/30 11:21
jun68ykt

総合スコア9058

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

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

退会済みユーザー

退会済みユーザー

2019/05/29 12:31

ご丁寧に説明いただきましてありがとうございます。大変わかりやすく、何が問題だったのかわかりました。
jun68ykt

2019/05/29 12:35

どういたしまして。解決されたようでよかったです ????
jun68ykt

2019/05/29 13:43

計算量が O(n) で済み、問題を(おそらく)解決するコードを追記しました。 A のインデクス 0 から始まる部分リストで、合計が0以下になる場合、その部分リストは、解となる部分リストから除外されるべき、という考えで、初めに開始インデクスを求めてから、次に終端を求めています。 これが分かるように、サンプルの A の先頭に要素を追加しています。 ちなみに、55 個の部分リストをくまなく調べるのは O(n^2) ですが、O(n) で済むものを O(n^2) で解くのは無駄なので、追記のほうを推奨します。
can110

2019/05/29 22:01

最後のコードだとA=[-1,-2]の場合に(-2, 1, 1)と誤答してしまうようです。
退会済みユーザー

退会済みユーザー

2019/05/30 00:33

@jun68ykt ご丁寧に追記いただきましてありがとうございます。 1点解釈について念の為確認させていただきたいのですが、この解法は分割統治法(大きな問題を効率的に解く手法の一つで、問題全体を同じ構造の小さな問題に再帰的に分割していき、簡単に解けるサイズにした上で解いていく方式)ではないという理解で合っていますでしょうか。 分割統治法でも解けると聞いたのですが、分割統治法とアルゴリズムの理解がまだ乏しく、伺いました。 別の質問をすべき場合はご指摘願います。
otolab

2019/05/30 01:57 編集

> A = [-1, 5, -10, 9, -4, 18, -10, 30, 23, -26, 37, 48, -19, -13, 31] A[3] + A[4] > 0なので、(125, 3, 11)になるはずです。 すんごい惜しいです。 > A = [18, -10, 30, 23, -26, 37, 48, -19, -13, 31] > なら > 112, 2, 6 設問もちょっと変で、A[0] + A[1] > 0なので、答えは(120, 0, 6)になるはずなんですが。
otolab

2019/05/30 01:59

ちなみに、さっき思い出して書いてみたら、16行くらいになりました。
jun68ykt

2019/05/30 02:51

@can110さん > 最後のコードだとA=[-1,-2]の場合に(-2, 1, 1)と誤答してしまうようです。 ご指摘ありがとうございます。 @otolabさん > すんごい惜しいです。 チェックいただき、ありがとうございます。 > A[3] + A[4] > 0なので、(125, 3, 11)になるはずです。 そうですかー、「左から足し込んで 0 以下の区間は捨てる」っていうのは駄目ですね。 後ほど出直します。。。
jun68ykt

2019/05/30 02:58

@tenjinさん >1点解釈について念の為確認させていただきたいのですが、この解法は分割統治法(大きな問題を効率的に解く手法の一つで、問題全体を同じ構造の小さな問題に再帰的に分割していき、簡単に解けるサイズにした上で解いていく方式)ではないという理解で合っていますでしょうか。 はい。少なくとも、大きい問題(このご質問でいえば A 全体)を再帰的に分割して、より小さい対象に対する問題に帰着させていくことはやっていないです。 昨日考える中で、 「再帰でもいけそうかな?」 とも思いましたが、昨日考えた中ではコードを書くに至りませんでした。。。
jun68ykt

2019/05/30 05:02

@皆さま stackoverflow に良さそうなコードをみつけたので回答に追記しました。こちらのコードを読んで、私も自身の浅学非才を猛省いたしたく。ありがとうございました。m(_ _)m
退会済みユーザー

退会済みユーザー

2019/05/30 10:53

@皆さま 質問は至らない部分が多くありましたが、大変深い議論をありがとうございました。多く学ばせていただきました。
guest

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

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

ただいまの回答率
85.48%

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

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

質問する

関連した質問