Conversation
| return true; | ||
| } | ||
| queue<NodesAndRange> nodes; | ||
| nodes.emplace(root, LONG_MIN, LONG_MAX); |
There was a problem hiding this comment.
long long に対応する最小値と最大値は LONG_LONG_MIN と LONG_LONG_MAX です。
C++11 以降の C++ では、 std::numeric_limits::min() と std::numeric_limits::max() を使用することをお勧めいたします。
また、 long はメモリモデルによってサイズが異なります。 LP64 では 64-bit、 LLP64 では 32-bit となります。
https://ja.wikipedia.org/wiki/64%E3%83%93%E3%83%83%E3%83%88
サイズを指定したい場合は int64_t 等を使用することをお勧めいたします。
There was a problem hiding this comment.
@nodchip
レビューありがとうございます。
long long に対応する最小値と最大値は LONG_LONG_MIN と LONG_LONG_MAX です。
型ごとに対応するものが存在しますね。気をつけます。
C++11 以降の C++ では、 std::numeric_limits::min() と std::numeric_limits::max() を使用することをお勧めいたします。
numeric_limits::min()のようにlong long型であるとはっきりと伝わるためでしょうか?
long はメモリモデルによってサイズが異なります。
資料もありがとうございます。データモデルという概念をまず知りませんでした。使用するコンパイラによってデータモデルが異なり結果としてプログラムに影響が出るのですね。
int32_tだとmax側の境界値を渡される場合に、if (node->val <= lower_value || node->val >= upper_value)の条件がうまく判定されなくなるのでint64_t にしました。
There was a problem hiding this comment.
numeric_limits::min()のようにlong long型であるとはっきりと伝わるためでしょうか?
あくまで個人的な感覚ですが、 LONG_LONG_MIN を使うと C 言語っぽく感じられるためだと思います。 std::numeric_limits<long long>::min() と書くと C++ っぽい感じられるので、そちらのほうが良いと思います。
| } | ||
|
|
||
| private: | ||
| bool CheckIsValidBst(TreeNode* node, long long lower_value,long long upper_value) { |
| } | ||
|
|
||
| private: | ||
| bool CheckIsValidBst(TreeNode* node, long long lower_value,long long upper_value) { |
There was a problem hiding this comment.
要素数の最大値が 104 のため、ノードが片側に偏って一直線になっていた場合、スタックオーバーフローを引き起こす可能性があります。
There was a problem hiding this comment.
@nodchip
この辺り苦手な箇所なのでじっくり調べてみました。
1TreeNodeあたりの大きさ確認
ツリーnodeが持っている要素は、int valとTreeNode *leftとTreeNode *right
intは2^32まで表せられる32bit。byteに変換すると32 / 8 で4byte
ポインターのサイズは、32bitコンピュータだと4byte、64bitコンピュータの場合は8byte
一般的なPCは64がほとんどと見かけました、なので合計4 + 8 * 2 = 20
https://www.naukri.com/code360/library/what-is-a-size-of-pointer-in-c
上記に加えて、paddingというものが存在する
kazukiii/leetcode@93bc473#r1677572697
知らなかったので下記を読んでみました。
https://www.geeksforgeeks.org/structure-member-alignment-padding-and-data-packing/
64ビットコンピュータでは8byteごとにメモリを管理しているので、この法則を守るため?にpaddingが使われているのですね。今回の場合だと、20byteを管理するために8byte*3の24byteが必要となりその間と埋めるために4byteのpaddingが必要になる。この8byteずつ管理しなさいというのは、プロセッサ側からの約束事。
合計24byteが1TreeNodeあたりに必要な大きさです。
再帰1巡回あたりに必要なサイズは
long longからint64_tに変換して話をします。
int64_tは2^64ビットを扱えるので、64ビット=8byte
CheckIsValidBst(TreeNode* node, int64_t lower_value, int64_t upper_value)なので24 + 8 + 8 で40byteが必要です。
10^4を考慮すると
10^4 * 40 = 400,000 byteが最大で必要となる。
すみませんここからオーバーするかどうかどう確認したらいいのかわかりませんでした🙇
There was a problem hiding this comment.
C 言語の場合の話となりますが、関数呼び出しを行うたびにスタックフレームが作られます。スタックフレームには以下のような値が格納されます。
・引数
・オート変数
・リターンアドレス
・リターン値設定アドレス
・レジスタ退避域
・一時変数
https://www.gaio.co.jp/gaioclub/compiler_blog10/
これを考えると Java の場合も引数以外の情報がスタックフレームに格納されるのではないかと思います。調べてみるとよいと思います。
また、スタックメモリのサイズは起動オプション -Xss で指定することができます。
https://docs.oracle.com/cd/F25597_01/document/products/jrockit/jrdocs/refman/optionX.html
他にも様々なオプションがあります。調べてみることをお勧めいたします。
There was a problem hiding this comment.
@nodchip
ありがとうございます!オート変数など初めて聞く名前などこの辺り調べてみます。
There was a problem hiding this comment.
CheckIsValidBst(TreeNode* node, int64_t lower_value, int64_t upper_value)なので24 + 8 + 8 で40byteが必要です。
一つ目の変数はポインタなので、合計で24 bytesではないですか?
There was a problem hiding this comment.
@liquo-rice
コメントありがとうございます。
nodeに対して下記のような構造体が定義されている場合、int valが4バイト、TreeNode *leftとTreeNode *rightがそれぞれ8バイト、そしてpaddingが4バイトで合計24にならないでしょうか?
struct TreeNode {
int val;
TreeNode *left;
TreeNode *right;
TreeNode() : val(0), left(nullptr), right(nullptr) {}
TreeNode(int x) : val(x), left(nullptr), right(nullptr) {}
TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {}
}
There was a problem hiding this comment.
TreeNode *leftとTreeNode *rightがそれぞれ8バイト
上と同じく、TreeNode* nodeの大きさは、8バイトです。
There was a problem hiding this comment.
@liquo-rice
ありがとうございます。
@nodchip @liquo-rice
下記読みました。
https://www.gaio.co.jp/gaioclub/compiler_blog10/
・引数
引数は3つで合計24byte
C++なのですが、Macのlldbというものを使ってサイズの確認をしてみました。
各引数のサイズは見ることができました。

・オート変数
auto をつけた変数のことなのですね。計算時に特別なものがあるのかと思いました。
今回使っていない
・リターンアドレス
8byte
・リターン値設定アドレス
こちらも64bitコンピュータとすると8byte
・レジスタ退避域
わからなかったのでChatGPtに聞いてみたところ16~64byte(環境による)ということでした。
・一時変数
今回は使っていない。lldb上でも表示されませんでした。
なので合計、1回の関数呼び出し56~106byteが必要。
56byte * 10^4 = 560,000byte = 560KB
106byte * 10^4 = 1,060,000byte = 1,060KB = 1.06 MB
Mac OSの場合Stackメモリーは8mbとございました、なので処理できると思いました🙇
| } | ||
|
|
||
| private: | ||
| struct NodesAndRange { |
| * TreeNode(int x, TreeNode *left, TreeNode *right) : val(x), left(left), right(right) {} | ||
| * }; | ||
| */ | ||
| class Solution { |
There was a problem hiding this comment.
書いてみたけど、探索中のnodeに紐づく左右だけとなってしまい
[5,4,6,null,null,3,7]を通すことができなかった。修正してみたがstep2やrecursion.cppのようになった
通らないのは子の値としか比較していないので、node->val < node->left->right->val みたいな子よりも下にいる子孫が二分探索木の条件を破る場合を見抜けないからだと思います。
wikipediaを見ると
binary tree, is a rooted binary tree data structure with the key of each internal node being greater than all the keys in the respective node's left subtree and less than the ones in its right subtree.
(強調引用者)
と書いてました。
修正の仕方は、左の子の値ではなく左部分木の最大値、右の子の値ではなく右部分木の最小値とnodeの値を比較すればよいのかなと。
実装してみました。 std::optinal使ってます。
struct TreeStatus {
bool valid;
optional<int> min;
optional<int> max;
};
class Solution {
public:
bool isValidBST(TreeNode* root) {
return computeTreeStatus(root).valid;
}
private:
static TreeStatus computeTreeStatus(const TreeNode* const node) {
if (!node) return {true, {}, {}};
if (!node->left && !node->right) return {true, node->val, node->val};
bool valid = true;
int min_value = node->val;
int max_value = node->val;
if (node->left) {
const TreeStatus left_status = computeTreeStatus(node->left);
valid &= left_status.valid && left_status.max.value() < node->val;
if (!valid) return {false, {}, {}};
min_value = left_status.min.value();
}
if (node->right) {
const TreeStatus right_status = computeTreeStatus(node->right);
valid &= right_status.valid && node->val < right_status.min.value();
if (!valid) return {false, {}, {}};
max_value = right_status.max.value();
}
return {true, min_value, max_value};
}
};There was a problem hiding this comment.
@fhiyo
レビューありがとうございます。そして解説もありがとうございます。
通らないのは子の値としか比較していないので、node->val < node->left->right->val みたいな子よりも下にいる子孫が二分探索木の条件を破る場合を見抜けないからだと思います。
今いるnodeに隣接している子としか比較できていないので通りませんでした。子よりも下にいる子孫の状態を保持しようとすると記載通り、step2やrecursion.cppのような方法しか思いつくことが出来ませんでした。
有効性を確認しならがら深く潜っていって有効な場合は、最大値or最小値を更新するのですね。ありがとうございます。
| } | ||
|
|
||
| private: | ||
| bool CheckIsValidBst(TreeNode* node, int64_t lower_value, int64_t upper_value) { |
|
拝見しました。 |
問題へのリンク
https://leetcode.com/problems/validate-binary-search-tree/description/
問題文(プレミアムの場合)
備考
次に解く問題の予告
Construct Binary Tree from Preorder and Inorder Traversal
フォルダ構成
LeetCodeの問題ごとにフォルダを作成します。
フォルダ内は、step1.cpp、step2.cpp、step3.cpp、recursion.cppとmemo.mdとなります。
memo.md内に各ステップで感じたことを追記します。