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 本手に入ります。
解法のアプローチ:二分探索
この問題は二分探索(バイナリサーチ)を使うことで効率的に解けます。ポイントは、「ある長さ mid で切り出したときの本数の合計が k 以上であれば、その長さは実現可能」という判定が単調であることです。つまり、短い長さほど多くのリボンが作れ、長い長さほど少なくなるため、実現可能な最大の長さを二分探索で絞り込むことができます。
アルゴリズムの手順
- left を 0、right をリボンの長さの最大値に初期化する
- left < right の間、以下を繰り返す
- mid := (left + right + 1) // 2(切り捨てだが +1 することで探索が右寄せになり、無限ループを防ぐ)
- sum(ribbonLen // mid for each ribbonLen) が k 以上なら、その長さは実現可能なので left := mid とする
- そうでなければ right := mid - 1 とする
- ループ終了後、left が 0 以外なら left を返す
- それ以外は -1 を返す
計算量は、各判定に O(n)(n はリボンの本数)、二分探索に O(log m)(m は最長のリボンの長さ)かかるため、全体で O(n log m) となります。
実装例(Python)
以下のコードで実際の動きを確認してみましょう。
def solve(ribbons, k): left = 0 right = max(ribbons) while left < right: mid = (left + right + 1) // 2 if sum((ribbonLen // mid for ribbonLen in ribbons)) >= k: left = mid else: right = mid - 1 if left: return left return -1 ribbons = [1, 2, 5, 7, 15] k = 5 print(solve(ribbons, k))
入力
[1, 2, 5, 7, 15], 5
出力
5
-
Pythonで最大頻度と同じ出現回数を持つ最短サブリストの長さを求める方法
数値のリスト nums が与えられたとします。nums 内で最も頻度が高い数値の出現回数を k とするとき、「その中で最も頻度の高い要素の出現回数も同じく k となるような、最短の部分リスト」の長さを求めるのが今回の課題です。問題の例例えば、入力が nums = [10, 20, 30, 40, 30, 10] の場合、出力は 3 になります。このリストでは 10 と 30 がそれぞれ 2 回出現しており、最大頻度は k = 2 です。ここで部分リスト [30, 40, 30] を選ぶと、30 を含み、かつ 30 の出現回数も 2 回である最短の部分リストとなります。そのため答えは長さ 3 で
-
Pythonで制約付きの建物の最大高さを求めるプログラム
問題の概要整数 n と制約リスト restrictions が与えられたとします。私たちは都市に n 棟の新しい建物を一列に建てようとしていますが、高さに関するいくつかの制限があります。建物には左から順に 1 から n までの番号が付けられており、各制約は restrictions[i] = (id_i, max_height_i) の形式で表され、「id_i 番の建物の高さは max_height_i 以下でなければならない」ことを意味します。建物の高さに関する都市の規則は以下のとおりです。各建物の高さは 0 以上でなければなりません。1 番の建物(最初の建物)の高さは必ず 0 です。隣接す