Pythonで積が等しくなるタプル(a×b=c×d)の個数を求めるプログラム
問題の概要
正の整数が重複なく格納された配列 nums が与えられます。このとき、a × b = c × d を満たすタプル (a, b, c, d) の総数を求めます。ただし、a、b、c、d はすべて nums の要素であり、4つの値は互いに異なる必要があります。
たとえば入力が nums = [2, 3, 4, 6] の場合、出力は 8 になります。条件を満たすタプルは次の 8 通りです。
(2, 6, 3, 4)、(2, 6, 4, 3)、(6, 2, 3, 4)、(6, 2, 4, 3)、(3, 4, 2, 6)、(4, 3, 2, 6)、(3, 4, 6, 2)、(4, 3, 6, 2)
解法のアプローチ
この問題は、「同じ積を持つペアがいくつ存在するか」を辞書で集計し、そこから組み合わせの数を計算することで効率よく解けます。手順は以下のとおりです。
- デフォルト値が 0 の空の辞書(マップ)
dicを用意する - 答えを格納する変数
ansを 0 で初期化する - i を 0 から(要素数 − 2)まで繰り返す
- j を i + 1 から(要素数 − 1)まで繰り返し、積
nums[i] * nums[j]の出現回数をdicに記録する
- j を i + 1 から(要素数 − 1)まで繰り返し、積
dicの各値 v に対して次を処理する- v が 1 の場合はスキップする(同じ積を持つペアが他に存在しないため)
- v から 1 を引く
- s := (v ÷ 2) × (8 + 8 × v) を計算する
ansに s を加算する
ansを整数として返す
計算式のポイント
同じ積 P を持つペアが k 個あるとき、その中から 2 つのペアを選ぶ方法は C(k, 2) = k × (k − 1) ÷ 2 通りです。さらに、選ばれた 2 つのペア (a, b) と (c, d) の並べ方は 8 通りあるため、合計は「C(k, 2) × 8」となります。コード内の式 (v ÷ 2) × (8 + 8 × v)(v = k − 1)は、この値と一致します。
Pythonでの実装例
それでは、実際の実装を見てみましょう。
from collections import defaultdict
def solve(nums):
dic = defaultdict(int)
ans = 0
for i in range(len(nums)-1):
for j in range(i+1, len(nums)):
dic[nums[i]*nums[j]] += 1
for v in dic.values():
if v == 1:
continue
v = v - 1
s = (v/2) * (8 + 8*v)
ans += s
return int(ans)
nums = [3, 4, 6, 2]
print(solve(nums))
入力
[3, 4, 6, 2]
出力
8
[3, 4, 6, 2] は [2, 3, 4, 6] と同じ要素の並べ替えであるため、先ほどの例と同じく 8 という結果が得られます。
計算量
すべてのペア (i, j) を一度ずつ調べるため、時間計算量は O(n²) です。また、積の出現回数を記録するため、空間計算量も最大で O(n²) となります。4重ループで全探索する O(n⁴) の素朴な方法に比べて、大幅な高速化が可能です。
-
Pythonで同じラベルを持つサブツリー内のノード数を求めるプログラム
ここでは、n個のノードからなる根付きの一般木を考えます。ノードには0からn-1までの番号が振られており、各ノードには小文字の英字ラベルが割り当てられています。ラベルは配列labelsとして与えられ(labels[i]がi番目のノードのラベル)、木は辺リストで表現されます。各辺eは[u, v]という形式で、uが親、vが子であることを意味します。 求めたいのは、サイズnの配列Aです。A[i]には「i番目のノードと同じラベルを持つ、そのサブツリー内のノードの総数」を格納します。 例えば、入力が次のような場合を考えてみましょう。 n = 5、label = ccaca のとき、出力は [3, 2,
-
Pythonで連続する部分配列の最大積を求めるプログラム
nums という配列が与えられたとき、少なくとも1つの要素を含む「連続した部分配列」の中から、要素の積が最大になるものを見つけて、その積を返すことを考えます。例えば、配列が [1,9,2,0,2,5] の場合、連続する部分配列 [1,9,2] の積が最大となるため、出力は 18 になります。 解法のアプローチ この問題は動的計画法(DP)を使って効率的に解くことができます。ポイントは、負の数同士を掛けると正の数になる可能性があるため、各位置における「最大積」と「最小積」の両方を追跡することです。 具体的な手順は以下の通りです。 max_list:nums と同じサイズのリストを作成し、0で初