Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Longest Common Prefix #47

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open

Longest Common Prefix #47

wants to merge 1 commit into from

Conversation

rihib
Copy link
Owner

@rihib rihib commented Nov 18, 2024

Longest Common Prefixを解きました。レビューをお願い致します。

問題:https://leetcode.com/problems/longest-common-prefix/
言語:Go

すでに解いた方々:
rm3as/code_interview#3

/*
時間:5分
まず最初に思いついた方法は各文字をそれぞれ見ていくという方法。素直に実装した。
*/
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.

てっきりfor文内のi >= len(word)の部分で止まるもんだと勘違いしてましたが、確かにそもそも空だとfor文自体が実行されないですね、、

*/

/*
トライ木でも解けるとのことなので、初実装してみた。トライ木といっても単に木なので特に問題なく実装できた。
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.

ありがとうございます。トライ木を実装したことがなくてちょうど最近トライ木がどういうものなのかを知ったのでいい機会だと思って実装しました。確かに少しやりすぎ感はありますが個人的には初実装だったので面白かったです👍

Choose a reason for hiding this comment

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

自分も最初に総なめする方法を思いついた後にTrieでもできそうだなと思いましたが、計算量が良くなるわけでもなく実装が複雑になるだけだなと思いTrieでは実装していないです。ただ、練習として書くことは良いと思います。ただ色々な解法で書けることに意味があるのではなくて、状況に即したコードを書けることに意味があると思うので、Step1の解法と比較してStep2はなにが良くて悪いのか、だったりどういうときにTrieを使うと良いのかまでを調べてようやくこの練習としては意味があるものになるのかなと思います。

Copy link

Choose a reason for hiding this comment

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

1 個目の文字列から始めて、 2 個目以降の文字列との共通部分まで削っていく、という書き方もありうると思いました。 C++ だと以下のようになります。

string common = strs[0];
for (const auto& str : strs) {
    auto p = std::mismatch(common.begin(), common.end(), str.begin(), str.end());
    common = common.substr(0, std::distance(common.begin(), p.first));
}
return common;

Copy link
Owner Author

Choose a reason for hiding this comment

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

@hayashi-ay トライ木のメリットとしてはprefix部分を共通化して保持できること、データ量が増えたとしても文字列検索にかかる時間はO(単語の文字数)で抑えられることだと思ってます。なのでその良さを発揮できるユースケースであれば役に立つという認識は持ってます。

この問題ではトライ木で解くメリットはないと思ってますが、練習として書きました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

@nodchip 確かにその書き方もありますね。ありがとうございます。

@rm3as
Copy link

rm3as commented Nov 18, 2024

正直自分はgoを見たこともほぼないのでreviewerとして不適切で申し訳ない(あまり気になるところは見つからなかった)のですが、強いていうならトライ木より文字列ソートの方が、オーバーヘッドやメモリ効率を考えると無難なような気がしました。

@rihib
Copy link
Owner Author

rihib commented Nov 18, 2024

@rm3as レビューありがとうございます。個人的にはメモリはともかく、時間的にはソートの方がコストかなと思いました。トライ木の場合は基本的には全部の文字を見ていくだけですが、ソートの場合は基本的にO(n log n)かかるので、、

@oda
Copy link

oda commented Nov 18, 2024

文字列のソートの場合は、比較をするのに文字列の長さかかることになるのでもう少しかかりますね。

@rihib
Copy link
Owner Author

rihib commented Nov 18, 2024

@oda 確かにそうですね、ありがとうございます。

@rihib
Copy link
Owner Author

rihib commented Nov 18, 2024

@oda 文字列の平均長をmとすると、オーダー的にはO(mn log n)になる感じですかね

そういえば基数ソート(またはトライ木)を使えばO(nm)でソートできそうですね。

Goだとsort.Stringsを使えば文字列同士のソートができて、内部的にはslices.Sortが呼ばれていて、クイックソートの改良版のPDQソートが使われているというのを前に調べたのを思い出しました。なのでやはりGoで文字列同士のソートをしようとするとO(mn log n)かかる感じになりそう?Goの実装読んだ方が良さそうですね。
#20

@oda
Copy link

oda commented Nov 18, 2024

オーダー記法は、極限がある関数の定数倍以下であるという指標なので、普通は最大長で抑えます。

そうですね。私は詳しく知りません。

@rm3as
Copy link

rm3as commented Nov 19, 2024

すみません、全部の文字を見ていくだけの手法は最初のNo1(垂直スキャン)の解き方も同じで、この場合だとトライ木は垂直スキャンに比べてオーバーヘッドが増えるだけのような(一方ソートはNが増えた際にメモリ効率面の良さがある)と思ったのでコメントしました

@rihib
Copy link
Owner Author

rihib commented Nov 19, 2024

@rm3as オーバーヘッドってこの場合何のことを指してますかね?

@rm3as
Copy link

rm3as commented Nov 19, 2024

メモリオーバーヘッド(新しいデータ構造を作っているということ)と、時間的オーバーヘッド(マップ操作やノード遷移が多いので)です

@rihib
Copy link
Owner Author

rihib commented Nov 19, 2024

@rm3as 時間的なオーバーヘッドについてはソートを使ったアプローチよりもトライ木の方があるとは言えないと思ってますというのが僕の意見としてあります。そもそもソートを使う方法はオーダー的に効率が悪いですし(ちなみに関係ないですが子ノードの探索については線形探索にするのもキャッシュ的に手だなとは思ってます)。メモリオーバーヘッドについては許容できるかはユースケース次第だと思ってます。

どちらにせよこの問題に限定して考えるのであれば、時間空間ともにO(文字数)の最適解があるので、ソートを使った方法vsトライ木という議論はあまり意味がないとは思ってます。この問題に限定しないのであればどちらが良いかはその条件次第だと思うので

@rm3as
Copy link

rm3as commented Nov 19, 2024

時間的オーバーヘッドがソートよりトライ木が大きいとは私も全く思ってません!(垂直スキャンとの比較でトライはオーダー同じだがオーバーヘッドがあると言ったつもりで、せっかくなら違う特性、メリットをもつ解法の方が、くらいの認識でした)。
ユースケースによるというのはおっしゃる通りだと思います

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants