set と辞書で「見たこと」を記録
同じ値があるかを毎回すべて探す必要はありません。set は重複を除き、dict は値と情報を対応付けます。
言語:Python / 計算量:平均 O(N)
前提:ソートで順序を作る
考え方
- set(a) は異なる値の集合。
- x in seen で存在を調べます。
- Counter は値ごとの出現回数を数えます。
具体例
[2,2,5,2] の集合は {2,5}。Counter では2が3回、5が1回です。
実装
from collections import Counter
a = [2, 2, 5, 2]
c = Counter(a)
print(c[2])注意する条件
集合の順序には頼らないでください。ハッシュ操作の O(1) は平均的な見積もりです。
確認問題
len(set([1,1,2,4,4])) は?
解答と理由
3
異なる値は1,2,4です。
実装課題
N と配列 A が2行。異なる値の種類数を求める。1 ≤ N ≤ 200000、|A_i| ≤ 10^9。
入力:
5
1 1 2 4 4
出力:
3参考実装
n = int(input())
a = list(map(int, input().split()))
print(len(set(a)))実戦につなげる補講
一度に全て読まなくて大丈夫です。今日は1節と1問から。
setと辞書は、質問に合わせて選ぶ
あるかないかだけならset。何個あるかなら辞書またはCounter。元の位置も必要なら値から位置のリストへの辞書を作ります。保存する情報を削りすぎると後から答えられません。
構築をループの外へ出す
毎回x in set(a)と書くと、質問ごとに集合を作り直します。s=set(a)を1回だけ実行してから質問します。平均で前処理O(N)、Q回の検索O(Q)。追加メモリは異なる値の数に比例します。
先に問い合わせ、あとで登録
右から新しい値xを読むとき、辞書に入っているのは過去の要素だけ、と決めます。これが不変条件です。target−xの個数を足してからxを登録すると、1組は右側の要素を読むときだけ数えられます。
段階式の追加演習
1. 集合で失う情報
a=[3,3,3,9]をsetにしたあと、3が何個だったか分かる?
ヒント
集合が保持するのは種類。
解答と理由
分からない。個数が必要ならCounter(a)や辞書で回数を保存する。
2. 構築コストの罠
Q個の質問に、毎回x in set(a)で答える。平均の計算量は?
ヒント
set(a)をいつ実行するか。
解答と理由
O(NQ)。集合を1回だけ作れば平均O(N+Q)。
3. 実装:過去の個数で組を数える
全探索のcount_pairsを、辞書で高速化して。重複する値があっても添字の組数を答える。
ヒント
seen.get(target-x,0)を足したあとseen[x]を増やす。
解答と理由
平均O(N)、メモリO(N)。同じ要素を2回使わない理由は、辞書には過去の要素だけがあるため。
def count_pairs(a, target):
seen = {}
answer = 0
for x in a:
answer += seen.get(target - x, 0)
seen[x] = seen.get(x, 0) + 1
return answer4. 手で追って反例を探す
a=[4,4,4],target=8。前のコードでanswerはどう変化する? 登録を先にすると?
ヒント
読む前の4の個数は0,1,2。
解答と理由
正しい順序なら0→1→3。登録が先だと自分自身も数えて1→3→6になり誤り。
公式過去問への橋渡し
ABC248 D / 公式解説
値ごとの出現位置を保存し、区間の境界を二分探索する。前処理と質問処理を分けて数える。
公式問題 / 公式解説見本を閉じて確認
- 何を繰り返しているかを説明できる
- 使える条件と使えない条件を1つずつ言える
- 見本を閉じて実装し、小さい入力で確かめられる