Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
65 changes: 65 additions & 0 deletions 0169.Majority-Element/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# 169. Majority Element

## step1

hashtbaleを使ってカウントする

https://github.com/erutako/leetcode/pull/4

https://github.com/NobukiFukui/Grind75-ProgrammingTraining/pull/34

https://github.com/huyfififi/coding-challenges/pull/19

ソートして中央値を求めるのは思いつかなかった

ヒープやクイックセレクトなどもおもしろい

https://github.com/ryosuketc/leetcode_grind75/pull/19


https://discord.com/channels/1084280443945353267/1192728121644945439/1244612442852556822

> Boyer–Moore majority vote algorithm というのがあって、これは手品

単純だけど工夫されていて個人的に好み

## Boyer–Moore majority vote algorithm

過半数(⌊n/2⌋ より多く)現れる値が **ちょうど1つある** とき、その値を **時間 O(n)、追加メモリ O(1)** で求める。`step3.py` はこの実装。

### やっていること

変数は2つだけ。

- **candidate**: いま「多数派かもしれない」とみなしている値
- **count**: candidate に対する「相殺のあと残っている勢力」のイメージ(非負整数)

配列を左から見て、各要素 `x` について次を繰り返す。

1. `count == 0` なら、`candidate = x` に差し替える(これまでの候補は相殺で尽きたので捨ててよい)。
2. `x == candidate` なら `count += 1`、そうでなければ `count -= 1`。

最後に残った `candidate` が答え。

### なぜ正しいか

**見方1: 「異なる値どうしをペアにして消す」操作と同じ**

`count` が減るステップは、「いまの候補の出現1つ」と「それと違う値の1つ」をペアにして列から取り除いたと考えてよい。`count` が 0 に戻るときは、その時点までの候補について「まだ相殺されていなかったコピー」がすべてペアで消えたことになる。

このとき **真の過半数の値を M** とすると、次が言える。

- 全体で M の個数は「M 以外」の個数より **常に多い**。
- どんなペア付けでも、「M と M 以外」をペアにできる回数は「M 以外の個数」で頭打ち。そのたびに M が1つずつ消費されるが、最終的に **M は必ず余る**(過半数だから)。

アルゴリズムが `count == 0` のたびに候補を捨てているのは、「これまでの部分列については、過半数になりうる値はまだどれか分からないので一旦リセットする」に相当するが、**グローバルに過半数の M は、すべての相殺が終わっても消えきらない**。だから走査全体のあとに残る候補は M でなければならない。

**見方2: 形式的な不変条件**

任意の時点で、「これまで読んだ接頭辞」と残りの列を合わせても、元の配列の過半数要素は **ちょうど M**(問題の仮定より一意)。アルゴリズムは「接頭辞の中で互いに異なる値のペアを繰り返し削除する」のと同値な処理になっており、**ペア削除は過半数を変えない**(M が過半数なら、削除後の列でもまだ M が過半数)。最終的に一行に縮めたとき残るのが M だから、出力される `candidate` は M。

**注意**: 「過半数が存在するとは限らない」配列では、最後の `candidate` が過半数とは限らない。そういう問題設定では、終了後にもう一度走査して `candidate` の頻度を数え、⌊n/2⌋ を超えるか確認する必要がある。

### 参照

https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_majority_vote_algorithm
11 changes: 11 additions & 0 deletions 0169.Majority-Element/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
from typing import List


class Solution:
def majorityElement(self, nums: List[int]) -> int:
num_to_count = {}
for n in nums:
num_to_count[n] = num_to_count.setdefault(n, 0) + 1
if num_to_count[n] > len(nums) // 2:
return n
return -1
23 changes: 23 additions & 0 deletions 0169.Majority-Element/step2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import collections
from typing import List


class Solution:
def majorityElement(self, nums: List[int]) -> int:
counter = collections.Counter(nums)
most_common = counter.most_common(1)[0]
return most_common[0] if most_common[1] > len(nums) // 2 else -1


class Solution:
def majorityElement(self, nums: List[int]) -> int:
candidate = None
count = 0
for num in nums:
if count == 0:
candidate = num
if num == candidate:
count += 1
else:
count -= 1
return candidate
16 changes: 16 additions & 0 deletions 0169.Majority-Element/step3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
from typing import List


class Solution:
def majorityElement(self, nums: List[int]) -> int:
candidate = None
count = 0
for num in nums:
if count == 0:
candidate = num
if candidate == num:
count += 1
else:
count -= 1

return candidate