Skip to content

Conversation

@hroc135
Copy link
Owner

@hroc135 hroc135 commented Nov 27, 2024

- inorderの中から目当てのものが見つかるまで
slices.Indexを呼ぶので、buildTree一回の計算量はO(n^2)。
buildTreeは再帰的にn回呼び出されるのでO(n^3)。
- 空間計算量: O(log n)。最悪でO(n)
Copy link

Choose a reason for hiding this comment

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

ビッグオー記法はもともと最悪計算量を表していると考えられるため、空間計算量 O(n) としてよいと思います。

slices.Indexを呼ぶので、buildTree一回の計算量はO(n^2)。
buildTreeは再帰的にn回呼び出されるのでO(n^3)。
- 空間計算量: O(log n)。最悪でO(n)
- 1スタックフレームサイズはO(1)でそれがlog n個、
Copy link

Choose a reason for hiding this comment

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

平衡二分木が与えられるとは来ていないため、高さが log n 個と考えるのはまずいと思います。

- 再帰の深さ: log n。最悪の場合、木が直線だとn
- 時間計算量: O(n^2)
- slices.Index -> O(n), buildTreeがn回呼ばれる
- 空間計算量: O(nlogn)
Copy link

Choose a reason for hiding this comment

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

スタックフレームは最大で n 個積まれるため、 O(n^2) ではないでしょうか?

また、スライスのインデックスを関数の引数として渡すことによって、スライスがコピーされることを避け、空間計算量を削減することはできますか?

Copy link
Owner Author

Choose a reason for hiding this comment

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

遅くなりましたが、スライスのインデックスを関数の引数として渡す方法を2bで書いてみました

Goのスライスは参照渡しなのでスライスの一部分を関数の引数として渡してもその時点ではコピーが作成されないと思いました

Copy link
Owner Author

Choose a reason for hiding this comment

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

自分で書いておいてなのですが、スライスのコピーが作られずずっと元のpreorder, inorderの一部を参照することになるのであれば、再帰の深さO(n)より、空間計算量はO(n)になりますか?

Copy link

Choose a reason for hiding this comment

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

Go は、はい、Python と違ってスライスがコピーされないんでしたね。
https://go.dev/blog/slices-intro

- n: 木の要素数
- 再帰の深さ: log n。最悪の場合、木が直線だとn
- 時間計算量: O(n^2)
- slices.Index -> O(n), buildTreeがn回呼ばれる
Copy link

Choose a reason for hiding this comment

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

あらかじめ inorder の値とインデックスを map で持たせることで、時間計算量を削減することはできますか?

}
```

- どういうユースケースでこのようなコードを書くことになるのか気になった
Copy link

Choose a reason for hiding this comment

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

これ、わりとユースケース不明ですね。どっちかというと、preorder と inorder の結果だけ転がっていて、元のデータが消滅してしまったので仕方がなく直している感じを受けました。


return buildTreeHelper(0, 0, len(preorder))
}
```
Copy link

Choose a reason for hiding this comment

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

もう少しいろいろな解き方が提示されていた記憶があります。Discord を見てみるといいかと思います。

Copy link
Owner Author

Choose a reason for hiding this comment

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

もう少し調べて2cと2dを追加しました

leftNodeCount := rootIndexInorder
rightNodeCount := len(inorder) - leftNodeCount - 1
root.Left = buildTree(preorder[1:leftNodeCount+1], inorder[:rootIndexInorder])
root.Right = buildTree(preorder[len(preorder)-rightNodeCount:], inorder[rootIndexInorder+1:])
Copy link

Choose a reason for hiding this comment

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

Goよくわかってないですが、lenが配列全体を舐めるとすると、L104とL106で2回ループすることになりますね。
これくらいはしょうがないのかもしれませんが…

Copy link
Owner Author

Choose a reason for hiding this comment

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

Goのlen(slice)は配列全体を舐めないのでO(1)で取得できます。sliceはインスタンス変数みたいな形で先頭要素へのポインタと長さを保持しているからです

}
leftNodeCount := rootInorderIndex - inorderStartIndex
root.Left = buildTreeRecursive(preorderStartIndex+1, inorderStartIndex, leftNodeCount)
root.Right = buildTreeRecursive(preorderStartIndex+leftNodeCount+1, rootInorderIndex+1, nodeCount-leftNodeCount-1)
Copy link

Choose a reason for hiding this comment

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

細かいですが、L145の再帰関数の第2引数は、rootInorderIndex+1よりinorderStartIndex+leftNodeCount+1とした方が、第1引数との統一感もありわかりやすいと思いました。

Copy link
Owner Author

Choose a reason for hiding this comment

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

はい、ここはどう表現しようか悩みました。結局コードの短さを取りましたが、おっしゃっていただいた方法の方が意図が伝わると思いました

return nil
}
root := &TreeNode{Val: preorder[0]}
stack := []*TreeNode{root} // 既に繋がれたノードのうち左の子を持つ可能性のあるものが積まれる
Copy link

Choose a reason for hiding this comment

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

「左の子を持つ可能性のあるもの」というより、こういう気持ちでした(stackはまだ決まってないから積んでるみたいなイメージでした)
nittoco/leetcode#37 (comment)

Copy link
Owner Author

Choose a reason for hiding this comment

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

なるほどです!

自分用メモ

「stack に入っているものは、.right がまだ決まっていないもの」

func push[E any](stack *[]E, elem E) {
*stack = append(*stack, elem)
}
```
Copy link

Choose a reason for hiding this comment

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

上のコメントと繋がりますが、こういう情報をstackで管理する方法もありますね
https://discord.com/channels/1084280443945353267/1247673286503039020/1300957769477918791

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants