前提・実現したいこと
配列で複数の整数が与えられた時に和が最も大きくなる範囲とその和を求めるプログラムを書いています。
例えば
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ページで確認できます。
またクリップした質問に回答があった際、通知やメールを受け取ることができます。
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。
2019/05/29 08:45
2019/05/29 10:29

退会済みユーザー
2019/05/29 12:37

退会済みユーザー
2019/05/29 13:06
2019/05/29 13:45
2019/05/30 01:36

回答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
総合スコア21960
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
上記のコードが正解ではないことを、コメントからご指摘、ありがとうございます。
自分でやろうと思ったのですが、たぶんどこかに良質な回答のコードがあるだろうと思って調べましたところ、以下を見つけました。
- Stackoverflow: Maximum sum sublist? の回答
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総合スコア9058
バッドをするには、ログインかつ
こちらの条件を満たす必要があります。

退会済みユーザー
2019/05/29 12:31
2019/05/29 22:01

退会済みユーザー
2019/05/30 00:33
2019/05/30 01:57 編集
2019/05/30 01:59

退会済みユーザー
2019/05/30 10:53

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