Pythonで解く「最後の石の重さ」問題 ― アルゴリズムと実装をわかりやすく解説
問題の概要
それぞれ正の整数の重さを持つ石がいくつか与えられます。毎ターン、最も重い2つの石を選んで砕きます。2つの石の重さを x、y(x ≤ y)とすると、砕いた結果は次の2通りのいずれかになります。
- x = y の場合: 2つの石はともに完全に破壊されます。
- x ≠ y の場合: 重さ x の石は完全に破壊され、重さ y の石は新しい重さ y − x となります。
この操作を繰り返すと、最後には最大で1個の石が残ります。残った石の重さを求めてください(石が1個も残らない場合は 0 を返します)。
具体例
例として、石の重さが [2, 7, 4, 1, 8, 1] の場合を考えてみましょう。このとき答えは 1 になります。
- まず最も重い 8 と 7 を砕くと、差は 1。配列は
[2, 4, 1, 1, 1]になります。 - 次に 4 と 2 を砕くと、差は 2。配列は
[2, 1, 1, 1]になります。 - 続いて 2 と 1 を砕くと、差は 1。配列は
[1, 1, 1]になります。 - 最後に重さ 1 の石同士を砕くと、両方とも消滅し、配列は
[1]となります。
したがって、最後に残る石の重さは 1 です。
解法のアプローチ
この問題は、ルールどおりにシミュレーションを行うことで解けます。手順は以下のとおりです。
- 配列 W が空であれば 0 を返す。
- W の要素が1つだけであれば、その値(W[0])を返す。
- W の要素数が2以上である間、以下を繰り返す。
- W をソートする。
- s1 := W の末尾の要素(最大値)、s2 := 末尾から2番目の要素とする。
- s1 = s2 であれば、両方の要素を W から取り除く。
- そうでなければ、s1 := |s1 − s2| と計算し、末尾の要素を取り除いた後、新たな末尾の要素を s1 で置き換える。
- ループ終了後、W に要素が残っていればその値を、空であれば 0 を返す。
Pythonでの実装例
それでは、実際のコードを見てみましょう。
class Solution(object):
def lastStoneWeight(self, stones):
"""
:type stones: List[int]
:rtype: int
"""
if len(stones) == 0:
return 0
if len(stones) == 1:
return stones[0]
while len(stones) > 1:
stones.sort()
s1, s2 = stones[-1], stones[-2]
if s1 == s2:
stones.pop()
stones.pop()
else:
s1 = abs(s1 - s2)
stones.pop()
stones[-1] = s1
return stones[-1] if stones else 0
ob1 = Solution()
print(ob1.lastStoneWeight([2, 7, 4, 1, 8, 1]))
入力と出力
入力:
[2, 7, 4, 1, 8, 1]
出力:
1
計算量と効率化のポイント
上記の実装では、各ターンごとにソートを行っているため、時間計算量は O(n² log n)(n は石の個数)となります。より効率化したい場合は、優先度付きキュー(ヒープ)を利用するとよいでしょう。Python の heapq モジュールは最小ヒープしか提供していないため、重さを負の値にして格納することで最大ヒープとして扱えます。この方法なら各操作を O(log n) に抑えられ、全体の計算量を O(n log n) まで改善できます。
ヒープを使った実装例
import heapq
class Solution(object):
def lastStoneWeight(self, stones):
heap = [-s for s in stones]
heapq.heapify(heap)
while len(heap) > 1:
y = -heapq.heappop(heap)
x = -heapq.heappop(heap)
if y != x:
heapq.heappush(heap, -(y - x))
return -heap[0] if heap else 0
状況に応じて、シンプルなソート方式とヒープ方式を使い分けることで、可読性とパフォーマンスのバランスを取ることができます。
-
Pythonでリストの最後の要素を取得する方法【初心者向け】
Pythonでリストの最後の要素を取得する基本方法 Pythonでは、リストをはじめとするシーケンス型オブジェクトに対して、インデックス(添字)を使って任意の要素にアクセスできます。インデックスは0から始まるため、最初の要素は「0」、2番目の要素は「1」というように指定します。 ここで重要になるのが負のインデックスです。負の数を指定すると、リストの末尾から逆順に数えて要素を取得できます。したがって、リストの最後の要素を取得したい場合は、インデックスとして -1 を指定するだけでOKです。 >>> L1 = [1, 2, 3, 4, 5] >>> print(
-
Pythonで月の最終日を取得する方法【calendarモジュールの使い方】
Pythonでは、標準ライブラリのcalendarモジュールを使うことで、指定した月の最終日(末日)を簡単に取得できます。calendarモジュールにはmonthrange(year, month)という便利なメソッドが用意されており、指定した年・月の「1日の曜日」と「その月の日数」をタプルで返します。この情報を組み合わせれば、月の最終日の日付や曜日を自在に求めることが可能です。 monthrange()メソッドの基本 calendar.monthrange(year, month)は、以下の2つの値を返します。 第1戻り値:その月の1日の曜日(0が日曜日、6が土曜日) 第2戻り値:その月の