Pythonでn個の商品を販売した後に残る異なるIDの最小数を求めるプログラム
問題の概要
数値のリスト items と整数値 n が与えられます。営業担当者は、さまざまなIDを持つ商品をカバンに入れて所持しており、カバンの中から最大で n 個の商品を販売(削除)することができます。このとき、n 個の商品を販売し終えた後にカバンへ残る「異なるIDの種類数」の最小値を求めるのが課題です。
入力例と出力例
たとえば、items = [2, 2, 6, 6]、n = 2 の場合を考えてみましょう。このとき出力は 1 になります。同じIDを持つ商品(ID 2 または ID 6)を2つまとめて販売すれば、残る商品のIDが1種類だけで済むためです。
解決のためのアプローチ
この問題を解くには、以下の手順に従います。
c: items 内の各要素(ID)ごとの出現頻度を記録するans: c のサイズ、つまり異なるIDの総数を初期値とするfreq: c 内のすべての頻度を昇順に並べ替えたリストを作成するi: 0 で初期化する- i が freq のサイズ未満である間、以下を繰り返す:
- freq[i] が n 以下の場合:n から freq[i] を引き(n -= freq[i])、ans を 1 減らす(ans -= 1)。これは「そのIDの商品をすべて売り切れる」ことを意味する
- そうでない場合:これ以上IDを減らせないため、この時点での ans を返して終了する
- i を 1 増やす
- ループを抜けたら 0 を返す
このアルゴリズムのポイントは、「出現回数が少ないIDから優先的に売り切る」という貪欲法(greedy)の発想です。在庫の少ないIDから排除していくことで、限られた販売回数 n でできるだけ多くのID種類を減らすことができます。
Pythonによる実装例
理解を深めるために、次の実装を見てみましょう。
from collections import Counter
class Solution:
def solve(self, items, n):
c = Counter(items)
ans = len(c)
freq = sorted(c.values())
i = 0
while i < len(freq):
if freq[i] <= n:
n -= freq[i]
ans -= 1
else:
return ans
i += 1
return 0
ob = Solution()
items = [2, 2, 6, 6]
n = 2
print(ob.solve(items, n))
入力
[2, 2, 6, 6], 2
出力
1
計算量の分析
まず Counter による頻度集計に O(len(items))、頻度リストのソートに O(m log m)(m は異なるIDの数)、最後の走査に O(m) かかります。したがって全体の時間計算量は O(len(items) + m log m)、空間計算量は O(m) となり、非常に効率的なアプローチです。
-
Pythonで色のマージ後に残る最小個数を求めるプログラム
問題概要 赤(R)、緑(G)、青(B)の3種類の色からなるリストを考えます。隣り合う異なる2つの色は、残りの「第3の色」1個に変換(マージ)できます。この変換を好きな順序で何度でも繰り返してよいとき、最終的に残る要素数の最小値を求めるのがこの問題です。 たとえば入力が colors = [G, R, G, B, R] の場合、次のように変換を進めることで最終的に1個まで減らせます。したがって出力は 1 となります。 解き方のアプローチ 一見すると状態探索が必要そうな問題ですが、実はXOR(排他的論理和)を使ったシンプルな判定だけで答えが求まります。手順は以下の通りです。 n := 色リス
-
Pythonで二分探索木(BST)の指定範囲内にあるノード数を求める方法
問題の概要 二分探索木(BST)が与えられ、さらに左側の境界値 l と右側の境界値 r が指定されます。このとき、木に含まれるすべてのノードの中で、値が l 以上 r 以下の範囲内にあるノードの個数を求めるのが目的です。 例えば、次のような木が与えられたとします。 このとき l = 7、r = 13 とすると、範囲内に含まれるノードは 8、10、12 の3つなので、出力は 3 になります。 アルゴリズムの考え方 スタックを使った反復的な深さ優先探索(DFS)で木をたどります。重要なポイントは、二分探索木の性質を活かして枝刈り(pruning)を行うことです。値が境界より小さいノードの左側の