From 1d81f1e36adb03e3c7593e0a6d4a53e1d3dc3d61 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Tue, 11 Aug 2026 20:31:20 +0900 Subject: [PATCH 1/2] step1,2 --- 0528.Random-Pick-with-Weight/memo.md | 32 ++++++++++++++++ 0528.Random-Pick-with-Weight/step1_bisect.py | 19 ++++++++++ 0528.Random-Pick-with-Weight/step1_naive.py | 20 ++++++++++ 0528.Random-Pick-with-Weight/step2_alias.py | 39 ++++++++++++++++++++ 0528.Random-Pick-with-Weight/step2_bisect.py | 17 +++++++++ 5 files changed, 127 insertions(+) create mode 100644 0528.Random-Pick-with-Weight/memo.md create mode 100644 0528.Random-Pick-with-Weight/step1_bisect.py create mode 100644 0528.Random-Pick-with-Weight/step1_naive.py create mode 100644 0528.Random-Pick-with-Weight/step2_alias.py create mode 100644 0528.Random-Pick-with-Weight/step2_bisect.py diff --git a/0528.Random-Pick-with-Weight/memo.md b/0528.Random-Pick-with-Weight/memo.md new file mode 100644 index 0000000..fbbf93d --- /dev/null +++ b/0528.Random-Pick-with-Weight/memo.md @@ -0,0 +1,32 @@ +# 528. Random Pick with Weight + +## step1 +9mぐらいでまず naive を書く。計算量 O(N)。 + +bisectを使って高速化。計算量 O(log N) + +## step2 +いい加減な名前で書いてしまったので改善 + +乱数の書き方を調べる: + +- random.randint: uniformと同じ書き方で書ける。もともと整数なのでこちらの方が良さそう +- random.choices: `return random.choices(list(range(len(self.prefix_sums))), cum_weights=self.prefix_sums)[0]` + - 内部で二分探索が走っている + - https://github.com/python/cpython/blob/219768ff531fc0686de623139562ee9f9537df98/Lib/random.py#L460 +- np.random.choice: O(N)だがバッチジョブだと高速化 + +--- + +Alias method + +https://leetcode.com/problems/random-pick-with-weight/solutions/671439/python-smart-o1-solution-with-detailed-e-r0gx/?envType=problem-list-v2&envId=7p55wqm + +https://en.wikipedia.org/wiki/Alias_method + +O(N)の前計算を行なっておくことでO(1)で生成できる + +乱数を考えると非効率な状況もある(e.g. p= 1/2, 1) + +## step3 +TODO: alias法を書く diff --git a/0528.Random-Pick-with-Weight/step1_bisect.py b/0528.Random-Pick-with-Weight/step1_bisect.py new file mode 100644 index 0000000..6453be1 --- /dev/null +++ b/0528.Random-Pick-with-Weight/step1_bisect.py @@ -0,0 +1,19 @@ +import random +import itertools +import bisect + +class Solution: + + def __init__(self, w: list[int]): + self.cumsum = list(itertools.accumulate(w)) + + + def pickIndex(self) -> int: + sampled = random.uniform(0, self.cumsum[-1]) + return bisect.bisect_left(self.cumsum, sampled) + + + +# Your Solution object will be instantiated and called as such: +# obj = Solution(w) +# param_1 = obj.pickIndex() diff --git a/0528.Random-Pick-with-Weight/step1_naive.py b/0528.Random-Pick-with-Weight/step1_naive.py new file mode 100644 index 0000000..7477fa1 --- /dev/null +++ b/0528.Random-Pick-with-Weight/step1_naive.py @@ -0,0 +1,20 @@ +import random +import itertools + +class Solution: + + def __init__(self, w: list[int]): + self.cumsum = list(itertools.accumulate(w)) + + + def pickIndex(self) -> int: + sampled = random.uniform(0, self.cumsum[-1]) + for i in range(len(self.cumsum)): + if sampled <= self.cumsum[i]: + return i + + + +# Your Solution object will be instantiated and called as such: +# obj = Solution(w) +# param_1 = obj.pickIndex() diff --git a/0528.Random-Pick-with-Weight/step2_alias.py b/0528.Random-Pick-with-Weight/step2_alias.py new file mode 100644 index 0000000..7850c94 --- /dev/null +++ b/0528.Random-Pick-with-Weight/step2_alias.py @@ -0,0 +1,39 @@ +import random + +class Solution: + + def __init__(self, weights: list[int]): + n = len(weights) + total = sum(weights) + scaled_prob = [w * n / total for w in weights] + + self.prob = [0.0] * n + self.alias = [0] * n + self.n = n + + small, large = [], [] + for i, p in enumerate(scaled_prob): + if p < 1.0: + small.append(i) + else: + large.append(i) + + while small and large: + s = small.pop() + l = large.pop() + self.prob[s] = scaled_prob[s] + self.alias[s] = l + scaled_prob[l] -= 1.0 - scaled_prob[s] + if scaled_prob[l] < 1.0: + small.append(l) + else: + large.append(l) + + while large: + self.prob[large.pop()] = 1.0 + while small: + self.prob[small.pop()] = 1.0 + + def pickIndex(self) -> int: + i = random.randint(0, self.n - 1) + return i if random.random() < self.prob[i] else self.alias[i] diff --git a/0528.Random-Pick-with-Weight/step2_bisect.py b/0528.Random-Pick-with-Weight/step2_bisect.py new file mode 100644 index 0000000..ce422e6 --- /dev/null +++ b/0528.Random-Pick-with-Weight/step2_bisect.py @@ -0,0 +1,17 @@ +import random +import itertools +import bisect + +class Solution: + + def __init__(self, weights: list[int]): + self.prefix_sums = list(itertools.accumulate(weights)) + + def pickIndex(self) -> int: + target_weight = random.randint(0, self.prefix_sums[-1]) + return bisect.bisect_left(self.prefix_sums, target_weight) + + +# Your Solution object will be instantiated and called as such: +# obj = Solution(w) +# param_1 = obj.pickIndex() From d98d45bac15d30e2e3169d217d0cf1bed07e0076 Mon Sep 17 00:00:00 2001 From: tom4649 Date: Fri, 14 Aug 2026 19:47:27 +0900 Subject: [PATCH 2/2] step3 --- 0528.Random-Pick-with-Weight/memo.md | 4 +- 0528.Random-Pick-with-Weight/step3_alias.py | 42 +++++++++++++++++++++ 2 files changed, 45 insertions(+), 1 deletion(-) create mode 100644 0528.Random-Pick-with-Weight/step3_alias.py diff --git a/0528.Random-Pick-with-Weight/memo.md b/0528.Random-Pick-with-Weight/memo.md index fbbf93d..04a3710 100644 --- a/0528.Random-Pick-with-Weight/memo.md +++ b/0528.Random-Pick-with-Weight/memo.md @@ -29,4 +29,6 @@ O(N)の前計算を行なっておくことでO(1)で生成できる 乱数を考えると非効率な状況もある(e.g. p= 1/2, 1) ## step3 -TODO: alias法を書く +詰まった点: +- scaled_weights で n 倍するのを忘れる +- 最後の while 処理を忘れる diff --git a/0528.Random-Pick-with-Weight/step3_alias.py b/0528.Random-Pick-with-Weight/step3_alias.py new file mode 100644 index 0000000..c5a4b14 --- /dev/null +++ b/0528.Random-Pick-with-Weight/step3_alias.py @@ -0,0 +1,42 @@ +import random + +class Solution: + + def __init__(self, weights: list[int]): + self.n = len(weights) + total = sum(weights) + scaled_weights = [w / total * self.n for w in weights] + + self.prob = [0.0] * self.n + self.alias = [0] * self.n + + small = [] + large = [] + for i, p in enumerate(scaled_weights): + if p < 1.0: + small.append(i) + else: + large.append(i) + + while small and large: + i_small = small.pop() + i_large =large.pop() + self.alias[i_small] = i_large + self.prob[i_small] = scaled_weights[i_small] + scaled_weights[i_large] -= 1.0 - scaled_weights[i_small] + if scaled_weights[i_large] < 1.0: + small.append(i_large) + else: + large.append(i_large) + + while large: + i = large.pop() + self.prob[i] = 1.0 + while small: + i = small.pop() + self.prob[i] = 1.0 + + + def pickIndex(self) -> int: + i = random.randint(0, self.n - 1) + return i if random.random() < self.prob[i] else self.alias[i]