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

108. Convert Sorted Array to Binary Search Tree #38

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

Conversation

@@ -0,0 +1,21 @@
/*
Time : O(N log N)
Space : O(N)
Copy link

Choose a reason for hiding this comment

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

Time: O(N)
Space: O(N^2)
ではないでしょうか?
sortedArrayToBST関数はnullptrを返す場合も含めると最悪で2N回(木が一直線の場合)呼ばれると思います。
空間計算量については、numsの部分コピーが、N + (N/2 + N/2) + (N/4 + N/4 + N/4 + N/4) + ... と作成されるのでO(N^2)だと思います

Copy link

Choose a reason for hiding this comment

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

空間計算量は時間計算量を超えることはないですね。
空間計算量ですが、この場合、必ず真ん中で分けているので、深さは log N で済みます。

Copy link

Choose a reason for hiding this comment

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

空間計算量については、numsの部分コピーが、N + N/2 + N/4 + ... と作成されるのでO(N)だと思います。

Copy link

Choose a reason for hiding this comment

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

@oda @nodchip
ご指摘ありがとうございます。まだ少し混乱しているので整理させてください

時間計算量: O(N logN)
auto left_nodes = vector<int>(nums.begin(), nums.begin() + center_index);でnumsの半分の長さのコピーを作成するのにO(N/2)時間かかる(ここを失念していました)。再帰の深さは平衡木であるため(ここも失念していました)、logNで済む。よって、(N/2 + N/2) + (N/4 + N/4 + N/4 + N/4) + ... = N * logNとなり、O(N logN)時間でできる

空間計算量: O(N)

numsの部分コピーが、N + N/2 + N/4 + ... と作成される

こちらも(N/2 + N/2) + (N/4 + N/4 + N/4 + N/4) + ... = N * logN では?と思ったが、よく考えたらこれはスタックメモリの話をしていて、スタックメモリは関数呼び出しが終了すると解放されるので、ある瞬間に積み上がる最大のスタックフレームの個数はlogN個で、その中身は大きさが N + N/2 + N/4 + ... なのでO(N)

parents_and_ranges.emplace(&node, 0, nums.size());
while (!parents_and_ranges.empty()){
auto [parent_node_pointer, left, right] = parents_and_ranges.top();
parents_and_ranges.pop();
Copy link

Choose a reason for hiding this comment

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

この2行を1行でできないかなと思ったのですが、C++のstack.pop()は何も返さないんですね

Copy link

Choose a reason for hiding this comment

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

stack.pop() が値を返した場合、参照返しにできず、値返しにせざるを得ないものの、パフォーマンス的に微妙だからできないようにしたのかなと想像しました。実際に理由は分かりません。委員会の文書に載っているのでしょうか…。

Copy link

Choose a reason for hiding this comment

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

この記事におっしゃる通りのことが書いてありました
https://cpptruths.blogspot.com/2005/10/why-does-stdstackpop-returns-void.html

TreeNode* parent_node = new TreeNode(nums[mid]);
*parent_node_pointer = parent_node;
parents_and_ranges.emplace(&parent_node->left, left, mid);
parents_and_ranges.emplace(&parent_node->right, mid + 1, right);
Copy link

Choose a reason for hiding this comment

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

pushでもいいのかなと思ったのですが、emplaceとpushの違いがわからなくて調べました。pushだと引数をstackのtopに追加するという意味で、emplaceだとstackのtopでオブジェクトを構築して置くという意味で合ってますか?
それでここでemplaceを使っている理由は.push(new TreeNode(&parent_node->left, left, mid))と書くより簡潔に書けるからということですか?

Copy link

@Ryotaro25 Ryotaro25 Feb 3, 2025

Choose a reason for hiding this comment

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

emplaceを用いている理由はtupleのコピーを発生させないためではないでしょうか。

Pushes a new element on top of the stack. The element is constructed in-place, i.e. no copy or move operations are performed.

https://en.cppreference.com/w/cpp/container/stack/emplace

pushを用いた場合は、stackに入れる前に&parent_node->left, left, midや&parent_node->right, mid + 1, right)のコピーが作られると思います。

Copy link

Choose a reason for hiding this comment

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

それでここでemplaceを使っている理由は.push(new TreeNode(&parent_node->left, left, mid))と書くより簡潔に書けるからということですか?

nit: new いりません。

Copy link

Choose a reason for hiding this comment

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

なるほどです。勉強になります!

if (nums.empty()) {
return nullptr;
}
int center_index = nums.size() / 2;

Choose a reason for hiding this comment

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

変数名はmiddleが一般的かと思いました🙇‍♂️
参考にcecnterとmiddleの違いをAIに聞いてみました。

「center」は「ぴったり中央」を意味するのに対し、「middle」は「だいたい真ん中らへん」を意味します。

class Solution {
public:
TreeNode* sortedArrayToBST(vector<int>& nums) {
stack<tuple<TreeNode**, int, int>> parents_and_ranges;

Choose a reason for hiding this comment

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

tuppleの代わりにstructを使うのも一つの方法だと思います。
googleのコーディング規約には下記のように記載ございます。

Prefer to use a struct instead of a pair or a tuple whenever the elements can have meaningful names.

参照
https://google.github.io/styleguide/cppguide.html#Structs_vs._Tuples

}
int center_index = nums.size() / 2;
auto node = new TreeNode(nums[center_index]);
auto left_nodes = vector<int>(nums.begin(), nums.begin() + center_index);

Choose a reason for hiding this comment

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

vectorをコピー生成を行っているので、indexで管理するのもありかと思いました。

再帰を使わずに実装。
https://github.com/colorbox/leetcode/pull/37
を説いたときにある程度理解したつもりだったが、ポインタのポインタを使おうとするとだいぶ混乱する。
Copy link

Choose a reason for hiding this comment

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

最後のポインターは「を保存する先」という意味になりますね。

Copy link

Choose a reason for hiding this comment

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

特に問題ないかと思います。

@@ -0,0 +1,21 @@
/*
Time : O(N log N)
Space : O(N)
Copy link

Choose a reason for hiding this comment

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

空間計算量については、numsの部分コピーが、N + N/2 + N/4 + ... と作成されるのでO(N)だと思います。

}

private:
TreeNode* sorted_array_to_bst_recursive(vector<int>& nums, int left, int right) {
Copy link

Choose a reason for hiding this comment

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

nums は内部で変更されないため、 const を付けたほうがよいと思います。個人的には、変更しない変数について、プリミティブ型は const をつけず、複合型は const を付けています。ただ、あくまで個人的な目安のため、所属するチームの平均的な書き方に合わせることをお勧めいたします。

Copy link
Owner Author

Choose a reason for hiding this comment

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

コメントありがとうございます、このnumsに対しては変更を加えないのでconstをつけるほうがよいですね。

parents_and_ranges.emplace(&node, 0, nums.size());
while (!parents_and_ranges.empty()){
auto [parent_node_pointer, left, right] = parents_and_ranges.top();
parents_and_ranges.pop();
Copy link

Choose a reason for hiding this comment

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

stack.pop() が値を返した場合、参照返しにできず、値返しにせざるを得ないものの、パフォーマンス的に微妙だからできないようにしたのかなと想像しました。実際に理由は分かりません。委員会の文書に載っているのでしょうか…。

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