Skip to content
Merged
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
89 changes: 89 additions & 0 deletions md/2-06_MajorityElement.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
# 2-06 MajorityElement
Author: WaveAlchemist
URL: https://leetcode.com/problems/majority-element/description/

Given an array nums of size n, return the majority element.
The majority element is the element that appears more than ⌊n / 2⌋ times. You may assume that the majority element always exists in the array.
Example 1:
Input: nums = [3,2,3]
Output: 3

Example 2:
Input: nums = [2,2,1,1,1,2,2]
Output: 2

# 1st
defaultdict nums_countを用いて,各要素の個数をカウント
各要素の個数が過半数を超えた時点でその要素を返す
所要時間 4min46s
実行時間 172ms

``` Python
class Solution:
def majorityElement(self, nums: List[int]) -> int:
# dictionary for counting numbers of each element
nums_count = defaultdict(int)
# count numbers contained in nums
for num in nums:
if not nums_count[num]:

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

defaultdictの場合はこの場合分け不要

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます.
https://docs.python.org/ja/3.6/library/collections.html#defaultdict-examples
には
それぞれのキーが最初に登場したとき、マッピングにはまだ存在しません。そのためエントリは default_factory 関数が返す空の list を使って自動的に作成されます。
とありますね

nums_count[num] = 0
nums_count[num] += 1
if nums_count[num] > (len(nums) // 2):
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちら len()がどのくらいコスト掛かるかはみておいても良いと思いました。
ループの中で使うより先に定義して置く方が良いと思います。
(すみません、細かい所は調べてみてください

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

コメントありがとうございます.
https://docs.python.org/ja/3/library/tracemalloc.html#pretty-top
↑を参考にして,割り当てメモリの差を調べてみると両ケースとも
first_size=280, first_peak=352
となりました
ただ,timeitを用いて調べると以下のように実行時間に差が出ますね.
事前定義をする場合 time: 0.7654874669999856
事前定義をしない場合 time: 0.9718373919999976

Copy link

@kzmkt kzmkt Jun 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

はい、ガベージコレクションの実装によるとは思いますが結果を内部的に使いまわせるかは恐らくNOです。(numsの長さが変わらない保証がないため)
→これ自体はその通りではありますが、listに対するlen()の実行は定数時間でした。🙇‍♂️
#34 (comment)

今回はここだけですが、近いコードを書くときに複数回len()を見たくなるケースが出てくるだろうという意味でも、ループ前に定義すると良いかと思います。

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(お節介ですが念の為
逆に長さが変わるなら中で持つ方が良いですが、これもやり方の問題で初めはlen(nums)みておいて、appendするときにnums_lenに+1するとかでも良いかなと思いました。

実装するもの次第かなと

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

listに対するlenは定数時間ではないですか?

Copy link

@kzmkt kzmkt Jun 29, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

すみません失礼、たしかに定数時間ですね。
嘘ついてました。(というかミスリードしてますね)
https://wiki.python.org/moin/TimeComplexity
CPythonだとこの辺りですね。
(↑で書いたような面倒をこちらで見るのは大変なので、コントロールされてると思うべきでした。)
https://github.com/python/cpython/blob/cda73a5af2ff064ca82140342b3158851d43868f/Objects/listobject.c#L438-L442

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

関数呼び出しのオーバーヘッドはあるでしょうが、私は len のほうが読みやすいと思います。

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

こちらでも似たような議論が起きました!
fhiyo/leetcode#38 (comment)

return num
```

# 2nd
discord上のほかの議論を参照
・Boyer-Moore majority vote algorithm
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ありがとうございます.
最初に出てきた要素が連続する際はcount += 1 そうでないときは-= 1
一度count = 0になったら次に出てきた要素がmajority_candidateになるというアルゴリズムと理解しました

要素が同じであればcountに+1をし,そうでなければ-1を行う
要素が過半数ない場合はcountが0をまたぐので,その時点で候補を変更する
https://leetcode.com/problems/majority-element/solutions/3676530/3-method-s-beats-100-c-java-python-beginner-friendly/

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

# 3rd
・kitakenさんのご指摘
defaultdictの場合はこの場合分け不要

``` Python
class Solution:
def majorityElement(self, nums: List[int]) -> int:
num_count = defaultdict(int)
for num in nums:
num_count[num] += 1
if num_count[num] > len(nums) // 2:
return num
```
# 4th
・kouさんのコメント
こちら len()がどのくらいコスト掛かるかはみておいても良いと思いました。
ループの中で使うより先に定義して置く方が良いと思います
timeitを用いて調べると以下のように実行時間に差が出現.
事前定義をする場合 time: 0.7654874669999856
事前定義をしない場合 time: 0.9718373919999976

``` Python
class Solution:
def majorityElement(self, nums: List[int]) -> int:
nums_count = defaultdict(int)
nums_length = len(nums)
for num in nums:
nums_count[num] += 1
if nums_count[num] > nums_length // 2:
return num
```

57 changes: 57 additions & 0 deletions python/2-06_MajorityElement.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@

# %%
class Solution:
def majorityElement(self, nums: List[int]) -> int:
# dictionary for counting numbers of each element
nums_count = defaultdict(int)
# count numbers contained in nums
for num in nums:
if not nums_count[num]:
nums_count[num] = 0
nums_count[num] += 1
if nums_count[num] > (len(nums) // 2):
return num

solution = Solution()
nums = [1,2,2,2,1]
print(solution.majorityElement(nums))
# %%
import timeit
from collections import defaultdict
from typing import List

class Solution1:
def majorityElement(self, nums: List[int]) -> int:
num_count = defaultdict(int)
length_num = len(nums)
for num in nums:
num_count[num] += 1
if num_count[num] > length_num // 2:
return num

class Solution2:
def majorityElement(self, nums: List[int]) -> int:
num_count = defaultdict(int)
for num in nums:
num_count[num] += 1
if num_count[num] > len(nums) // 2:
return num

# テストデータを用意
test_data = [3, 2, 3, 1, 3, 4, 3, 3]

# 計測対象の関数を呼び出すラッパー関数を定義
def test_solution1():
solution = Solution1()
return solution.majorityElement(test_data)

def test_solution2():
solution = Solution2()
return solution.majorityElement(test_data)

# timeitを使用して実行時間を計測
time_solution1 = timeit.timeit(test_solution1, number=100000)
time_solution2 = timeit.timeit(test_solution2, number=100000)

print(f"Solution1 time: {time_solution1}")
print(f"Solution2 time: {time_solution2}")