Pythonで「最大消去値」を求めるプログラム ― スライディングウィンドウ法による解説
問題の概要
正の整数のみを含む配列 nums が与えられます。この中から要素がすべて一意(重複なし)である部分配列をちょうど1つ選んで「消去」し、その部分配列に含まれる要素の合計値をスコアとして得ます。求めたいのは、この操作で取得できるスコアの最大値です。
例えば、入力が nums = [6,3,2,3,6,3,2,3,6] の場合、出力は 11 になります。これは、最適な部分配列が [6,3,2] または [2,3,6] のいずれかであり、どちらも合計が 11 になるためです。
解き方のアプローチ:スライディングウィンドウ
この問題はスライディングウィンドウ(尺取り法)を使うことで効率的に解けます。左端 l と右端 r の2つのポインタを管理し、ウィンドウ内の要素が常に重複しない状態を保ちます。
用意する変数は次のとおりです。
seen:各要素の値と、それが最後に出現したインデックスを記録する辞書(マップ)ans:現時点での最大スコア(初期値 0)sum:現在のウィンドウ内の要素の合計(初期値 0)l:ウィンドウの左端インデックス(初期値 0)
配列の各インデックス r とその値 x に対して、以下の手順で処理を進めます。
xがすでにseenに存在する場合は、重複を解消する必要があります。前回の出現位置index = seen[x]を取得し、l <= indexの間、ウィンドウ左端の要素を取り除いていきます(seenから削除し、sumからも減算、その後lを1つ進める)。seen[x] = rとして現在位置を記録します。sum += xで現在の要素を合計に加算します。ansをansとsumの大きい方の値で更新します。
ループが終わったら ans を返せば、それが答えになります。
Pythonでの実装例
実際のコードは次のようになります。
def solve(nums):
seen = dict()
ans = sum = 0
l = 0
for r, x in enumerate(nums):
if x in seen:
index = seen[x]
while l <= index:
del seen[nums[l]]
sum -= nums[l]
l += 1
seen[x] = r
sum += x
ans = max(ans, sum)
return ans
nums = [6,3,2,3,6,3,2,3,6]
print(solve(nums))
実行結果
入力:
[6,3,2,3,6,3,2,3,6]
出力:
11
計算量について
このアルゴリズムでは、各要素はウィンドウへの追加・削除ともに高々1回しか行われません。そのため、時間計算量は O(n)、要素の出現位置を記録する辞書 seen の分だけ空間計算量も O(n) となり、大きな入力に対しても高速に動作します。
-
Pythonで同じ長さのリボンをk本切り出せる最大の長さを求めるプログラム
正の整数のリスト(各リボンの長さを表します)と整数 k が与えられます。リボンは何回でも切ることができるので、長さ r のリボンをちょうど k 本作れるような最大の r を求めてください。そのような解が存在しない場合は -1 を返します。たとえば、入力が ribbons = [1, 2, 5, 7, 15]、k = 5 の場合、出力は 5 になります。長さ 15 のリボンを長さ 5 の 3 本に切り分け、長さ 7 のリボンは長さ 2 と 5 に切り分けます。さらに長さ 5 のリボンがもう 1 本あるため、合計で長さ 5 のリボンが 5 本手に入ります。解法のアプローチ:二分探索この問題は二分探
-
Pythonで制約付きの建物の最大高さを求めるプログラム
問題の概要整数 n と制約リスト restrictions が与えられたとします。私たちは都市に n 棟の新しい建物を一列に建てようとしていますが、高さに関するいくつかの制限があります。建物には左から順に 1 から n までの番号が付けられており、各制約は restrictions[i] = (id_i, max_height_i) の形式で表され、「id_i 番の建物の高さは max_height_i 以下でなければならない」ことを意味します。建物の高さに関する都市の規則は以下のとおりです。各建物の高さは 0 以上でなければなりません。1 番の建物(最初の建物)の高さは必ず 0 です。隣接す