Pythonで積が正になる部分配列の最大長を求めるアルゴリズムを解説
問題概要
整数の配列 nums が与えられたとき、「すべての要素の積が正になる部分配列(サブ配列)」のうち、最も長いものの長さを求めることを考えます。
例えば、入力が nums = [2, -2, -4, 5, -3] の場合、答えは 4 になります。先頭の4つの要素からなる部分配列 [2, -2, -4, 5] の積は 2 × (-2) × (-4) × 5 = 80 となり、正の値になるためです。ここに5番目の要素 -3 を加えると負の数が3つになり積が負になってしまうため、長さ4が最大となります。
解法のポイント
この問題を効率よく解く鍵は、次の2つの性質にあります。
- 負の数の個数が0個または偶数個なら、その区間全体の積は必ず正になる
- 0 が1つでも含まれると積は必ず 0 になるため、0 を境界として配列をいくつかの区間に分割して考える
負の数が奇数個含まれる区間では、区間の端にある「最初の負の数」または「最後の負の数」のどちらか一方を除外すれば、残りの負の数は偶数個となり、積を正にできます。除外する位置によって得られる長さが異なるため、両方の候補を比較して大きい方を採用します。
アルゴリズムの手順
1. 補助関数 util(s, e)
区間 [s, e] について、積が正になる最大の部分配列の長さを返す関数を定義します。
- 負の数のカウンタ
negを 0、最初の負の数の位置nsと最後の負の数の位置neを -1 で初期化します。 - i を s から e まで走査し、
nums[i]が負であればnegを +1 し、nsが未設定ならns = iとし、さらにne = iを毎回更新します。 negが 0 または偶数であれば、区間全体をそのまま使えるのでe - s + 1を返します。negが奇数であれば、max(e - ns, ne - s)、すなわち「最初の負の数を除いた長さ」と「最後の負の数を除いた長さ」の大きい方を返します。
2. メイン処理 solve()
- 答え
ansを 0、区間の開始位置sと終了位置eを -1 で初期化します。 - 配列を先頭から走査し、0 以外の要素に出会ったとき、まだ区間が始まっていなければ
sを設定します。 - 0 を検出した時点で、直前までの区間 [s, i-1] に対して
util()を呼び出し、その結果をansに反映したうえでs,eをリセットします。 - 走査完了後に未処理の区間が残っている場合は、配列末尾までの区間に対して同じく
util()を呼び出します。 - 最後に
ansを返します。
実装例
以上の考え方をPythonで実装すると、次のようになります。
def util(s, e):
neg = 0
ns, ne = -1, -1
for i in range(s, e + 1):
if nums[i] < 0:
neg += 1
if ns == -1:
ns = i
ne = i
# 負の数が0個または偶数個なら区間全体が利用可能
if neg % 2 == 0:
return e - s + 1
else:
# 奇数個の場合は、端の負の数をどちらか1つ除外する
return max(e - ns, ne - s)
def solve(nums):
ans = 0
s, e = -1, -1
for i in range(len(nums)):
if nums[i] != 0 and s == -1:
s = i
elif nums[i] == 0 and s != -1:
e = i - 1
ans = max(ans, util(s, e))
s = -1
e = -1
if s != -1 and e == -1:
e = len(nums) - 1
ans = max(ans, util(s, e))
return ans
nums = [2, -2, -4, 5, -3]
print(solve(nums))
入力
[2, -2, -4, 5, -3]
出力
4
計算量
各要素は高々2回程度しか走査されないため、時間計算量は O(n)、追加で必要な空間計算量は O(1) で済みます。0 を境に配列を分割し、各区間内で負の数の偶奇だけを管理するシンプルな戦略が、この問題を線形時間で解くポイントです。
-
Pythonで最大の成功確率を持つパスを見つけるプログラムの実装方法
問題の概要 n 個のノード(ノードには 0 から順に番号が振られています)からなる無向重み付きグラフを考えます。このグラフは辺リスト(edge list)として入力され、各辺 e には「その辺を通過する際の成功確率」probability[e] が割り当てられています。さらに、開始ノード(start)と終了ノード(end)も与えられます。 求めたいのは、start から end へ移動するときに成功確率が最大となる経路であり、答えとしてその成功確率を返します。経路がひとつも存在しない場合は 0 を返してください。 たとえば、次のような入力が与えられたとします。 この場合の出力は 0.25
-
Pythonで解く最大積部分配列問題【動的計画法の実装例】
問題の概要 整数配列 nums が与えられたとき、配列内の連続する部分配列(少なくとも1つの要素を含む)の中で、積が最大になるものを見つける問題です。 例えば、配列が [2,3,-2,4] の場合、出力は 6 になります。これは連続する部分配列 [2,3] の積 2×3=6 が最大だからです。 解法のアプローチ この問題は動的計画法(DP)を活用して解くのが効果的です。重要なポイントは、配列に負の数が含まれる可能性があることです。負の数同士を掛け合わせると正の数になるため、それまでの「最小の積」が突然「最大の積」に変わることがあります。 そこで、各インデックスにおいて「その位置で終わる部分配