Conversation
| leftFound := hasPathSum(root.Left, remaining) | ||
| rightFound := hasPathSum(root.Right, remaining) | ||
| return leftFound || rightFound |
There was a problem hiding this comment.
return hasPathSum(root.Left, remaining) || hasPathSum(root.Right, remaining)とした方が短絡評価が起こるので良さそうです。
There was a problem hiding this comment.
短絡評価という言葉を知らなかったです。ブール演算子による計算において、第一引数を評価して結果が定まらない場合のみ第二引数を評価するということですね
ありがとうございます
| } | ||
|
|
||
| func hasPathSum(root *TreeNode, targetSum int) bool { | ||
| stack := make([]nodePathSum, 0, 10000) |
There was a problem hiding this comment.
スライスに入りうる最大要素数を知っている場合にはキャパシティを指定した方が再割り当てが起きずパフォーマンスが向上することが多いので、キャパシティを指定しました
ただ、今見返したら最大要素数は1e4ではなく5e3でした。木の要素数が最大で5e3で、木が右側への直線の時に、各ノードがnilノードも合わせて二つの子ノードを持ち、両方スタックに積まれるので 2 * 5e3 = 1e4個スタックに入ることになるかと思ったのですが、popされることを忘れていたので、5e3でした
There was a problem hiding this comment.
capacityを大きく割り当てるとインプットのサイズが小さい時に悪影響がありそうでしょうか?
There was a problem hiding this comment.
はい、5e3個分のキャパシティを割り当てたのに実際の要素数が0だと5e3だけ無駄にメモリ使用量として数えられてしまうことになります。
つまり、キャパシティを5e3に設定すると、
- 木の要素数が大きいと、メモリ領域の再割り当てを避けられるので実行時間的に良い
- 木の要素数が小さいと、無駄にメモリ領域を割り当てることになってしまい、メモリ使用量的に良くない
というトレードオフがあり、今回は1の場合に実行時間を抑えたくてキャパシティを設定しました。
というところまで吟味しました、というところまで書いておくべきでした
There was a problem hiding this comment.
なるほど、まず、本当にそこの速度が大事なのかと全体が何秒速くなっているかを考えるか試してみるかして下さい。
速度が大事で速くしたいといっても、一般にボトルネックでないところを速くしてもそれほど効果はありません。
次、メモリー使用量です、ポインターと int なので環境によりますがサイズは 8 + 4 バイトですか? たぶん、パディングされて16バイトでしょうか。それで、1万とっているので、これ、160キロバイトですかね。環境にもよりますがL1キャッシュのサイズを超えていそうです。ランダムアクセスではないのでかもしれませんが遅くなるかも。
最後に、10000という数がコードに現れると疑問が湧きます。「わざわざデフォルト引数から動かさなくてはいけなかったはずだ。どれくらいのコストを払ってこの数字を決めたのか、どれくらい動かしてはいけないのか、たとえば、1週間実験を繰り返した結果こうしたほうがいいと分かったのか、あるバグがあって特定条件で遅くなるのでこの数字にしておく必要があるのか。」コメントつけておいて欲しいですね。
There was a problem hiding this comment.
メモリー確保などに分解して時間を測って、上の表と比較をすると面白いかもしれません。
「精進」は、2010年前後の JOI 勢がいい始めた気がしています。
もともとのプログラミングコンテストや競技プログラミング同好会では聞かなかった気がしますね。
だいたいこれが競技プログラミング同好会の次の次あたりの世代です。
There was a problem hiding this comment.
https://touyoubuntu.hatenablog.com/entry/20111216/1323992210
精進について2011年の言及を発見しました。
やはり、JOI (2010-2011) の合宿から広まったということで正しいようです。
There was a problem hiding this comment.
なんと、考察記事まであるとは。想像以上に界隈で使われていたようですね。
There was a problem hiding this comment.
2005年に ICPC の世界出場メンバーが私に向かって精進と書いているのを見つけました。
実は精進という言葉もっと歴史があるのかしら。
| #### 2c スタックDFS nilノードもスタックに入れる | ||
| - スタックDFSでnilノードもスタックに入れる方法。 | ||
| これにより、条件分岐が少なくなる。 | ||
| 一方、nilノードもスタックに入れるので、最悪の場合、一本のパスだけからなる二分木だとスタックの要素数が木の要素数の倍になってしまう |
There was a problem hiding this comment.
速度は実験してみるといいと思います。分岐予測の失敗もそこそこコストがかかります。
そもそも論として、速度にこだわる必要があるかは状況次第です。
Python は C/C++ などから50倍くらい遅いので、それを使っている時点でそこの速度が問題でない可能性が高いです。
There was a problem hiding this comment.
@nittoco
ここの文間違っていました!
#24 (comment) でも記載しましたが、stackからは毎ループ1つpopされるので、常に2-1=1要素数が増えます。なので、一本のパスだけからなる二分木だと、「スタックに入った通算要素数」は木の要素数の倍になりますが、「スタックに入っている瞬間最大要素数」は木の要素数になるはずです
There was a problem hiding this comment.
勝手に自分で文を「スタックに入った通算要素数」で解釈してしまってました。確かに要素数というと瞬間要素数を指すのが一般的かもしれないですね、ありがとうございます。
分岐予測は確かに頭になかったです。言語間の比較や、分岐予測も含めてコストを考えてみます。
| if found { | ||
| return true | ||
| } |
There was a problem hiding this comment.
下のliquoriceさんのレビューでもありますが、短絡評価になるよう実装すればここの複雑なことをしなくても早めにreturnできますね。
https://leetcode.com/problems/path-sum/description/