この記事は、2021/08/12 に正式に開始された「LeetCode を征服する」シリーズ記事の 1 つです。LeetCode の一部の質問はロックされているため、このシリーズは、少なくともロックされていないすべての質問がクリアされるまで継続されます。LeetCode はまだ新しい質問を作成しているため、このシリーズの終了日は永遠になる可能性があります。この問題解決シリーズでは、さまざまな問題解決のアイデアとその最適化について説明するだけでなく、さまざまなプログラミング言語を使用して問題を解決する一般的な解決方法についてもまとめます。対応するアルゴリズム テンプレート。
PC 上でのコード ファイルの実行、デバッグ、共有を容易にするために、関連するウェアハウスも確立しました: https://github.com/memcpy0/LeetCode-Conquest。このウェアハウスでは、元の LeetCode 質問へのリンク、解決策コード、解決策記事へのリンク、類似質問の概要、一般的な解決策の概要などだけでなく、元の質問の頻度などの重要な情報も確認できます。および関連会社。他に好ましい解決策がある場合は、他の人と共有できます。
この一連の記事の内容はいつでも更新および変更される可能性があるため、リマインダーとして「LeetCode を征服する」シリーズの記事の目次をフォローして保存しておいてください。
root を持つ二分木がある場合root
、各ノードの深さは、そのノードからルートまでの最短距離になります。
元のツリーの最も深いノードをすべて含む最小のサブツリーを返します。
ツリー全体のノードの中で最も深いノードがある場合、そのノードは最も深いと言えます。
ノードのサブツリーは、そのノードとそのすべての子孫のセットです。
例 1:
输入:root = [3,5,1,6,2,0,8,null,null,7,4]
输出:[2,7,4]
解释:
我们返回值为 2 的节点,在图中用黄色标记。
在图中用蓝色标记的是树的最深的节点。
注意,节点 5、3 和 2 包含树中最深的节点,但节点 2 的子树最小,因此我们返回它。
例 2:
输入:root = [1]
输出:[1]
解释:根节点是树中最深的节点。
例 3:
输入:root = [0,1,3,null,2]
输出:[2]
解释:树中最深的节点为 2 ,有效子树为节点 2、1 和 0 的子树,但节点 2 的子树最小。
ヒント:
- ツリー内のノードの数は
[1, 500]
範囲内です。 0 <= Node.val <= 500
- 各ノードの値は一意です。
注:この質問は Leetcode 1123 と重複しています: https://leetcode-cn.com/problems/lowest-common-ancestor-of-deepest-leaves
解決策 1 再帰
上の図 (例 1) を見ると、このツリーのノードは3、5、2、3、5、2です。3 、5 、2はすべて最も深いリーフ ノード7, 4 7,47 、4の共通の祖先ですが、ノード2 のみです 22は最も新しい共通祖先です。
探しているノードが左側のサブツリーにのみ存在する場合、最新の共通祖先も左側のサブツリーにのみ存在する必要があります。この質問では、左側のサブツリーの最大深さが右側のサブツリーの最大深さよりも大きい場合、最も深いリーフ ノードは左側のサブツリーにのみ存在するため、最新の共通祖先は左側のサブツリーにのみ存在します。一方、右側のサブツリーの最大深さが左側のサブツリーよりも大きい場合、最も深いリーフ ノードは右側のサブツリーのみに存在するため、最新の共通祖先は右側のサブツリーのみに存在します。
左右のサブツリーの最大深さが同じ場合はどうなるでしょうか? 現在のノードは最新の共通祖先である必要がありますか? 不確かな。たとえば、ノード1 11の左右のサブツリーの最も深い葉ノードは0, 8 0,8 です。0 、8の深さは2 2です2ですが、深さはグローバル最大深さため、ノード1 11は答えではありません。
上記の議論に基づくと、正しいアプローチは次のとおりです。
- グローバル最大深さmaxDepth \textit{maxDepth}を維持しながら、このバイナリ ツリーを再帰します。最大深度。
- 通過深度「通過」時の深度dept h は、現在のノードの深さを表すために使用されます。
- 「戻る」とき、現在のサブツリーの最も深いリーフ ノードの深さをアップロードします。
- 左側のサブツリーの最も深いリーフ ノードの深さをleftMaxDepth \textit{leftMaxDepth}とします。leftMaxDepth、右側のサブツリーの最も深いリーフ ノードの深さはrightMaxDepth \textit{rightMaxDepth}結果leftMaxDepth = rightMaxDepth = maxDepth \textit{leftMaxDepth}=\textit{rightMaxDepth}=\textit{maxDepth}左最大深さ=右最大深さ=maxDepth を選択し、現在のノードへの応答を更新します。これは答えが見つかったことを意味するものではないことに注意してください。後でより深い葉ノードが見つかった場合、答えは更新されます。
class Solution {
public:
TreeNode *subtreeWithAllDeepest(TreeNode *root) {
TreeNode *ans = nullptr;
int max_depth = -1; // 全局最大深度
function<int(TreeNode*, int)> dfs = [&](TreeNode *node, int depth) {
if (node == nullptr) {
max_depth = max(max_depth, depth); // 维护全局最大深度
return depth;
}
int left_max_depth = dfs(node->left, depth + 1); // 获取左子树最深叶节点的深度
int right_max_depth = dfs(node->right, depth + 1); // 获取右子树最深叶节点的深度
if (left_max_depth == right_max_depth && left_max_depth == max_depth)
ans = node;
return max(left_max_depth, right_max_depth); // 当前子树最深叶节点的深度
};
dfs(root, 0);
return ans;
}
};
複雑さの分析:
- 時間計算量: O ( n ) \mathcal{O}(n)O ( n )。各ノードは 1 回だけ訪問されます。
- 空間計算量: O ( n ) \mathcal{O}(n)O ( n )。最悪の場合、バイナリ ツリーはチェーンであり、再帰には O(n)\mathcal{O}(n)O(n) 個のスタック スペースが必要になります。
解決策 2 ボトムアップ
各サブツリーを「サブ問題」として扱う代わりに、グローバル変数を使用することもできます。つまり、各サブツリーについて次のことを知る必要があります。
- このサブツリーの最も深いリーフ ノードの深さ。これは、バイナリ ツリー全体の観点から見た深さではなく、このサブツリー内のリーフの深さを指します。このサブツリーの高さに相当します。
- このサブツリーの最も深い葉ノードの最も近い共通祖先lca \textit{lca}lca。
分類の議論:
- サブツリーのルートノードをノードノードとするノードノード、ノードノード_ノードeの左側のサブツリーの高さはleftHeight \textit{leftHeight}leftHeight、ノード ノードノードの右側のサブツリーの高さはrightHeight \ textit{rightHeight}右高さ。
- 結果左 H 8 > 右 H 8 leftHeight>rightHeight左の高さ_ _ _ _ _>rightHeight + 1 \textit { leftHeight } + 1の場合、サブツリーの高さはleftHeight + 1 \textit{leftHeight} + 1 になります。左高さ+1、lca \textit{lca}lca は、左のサブツリー\textit{lca}の lca です。lca。
- 結果leftHeight < rightHeight \textit{leftHeight} < \textit{rightHeight}左高さ<rightHeight の場合、サブツリーの高さは右 H 8 + 1 rightHeight+1正しい高さ_ _ _ _ _ _ _+1、lca lcal c aは右サブツリーのlca lcal c a。
- leftHeight = rightHeight \textit{leftHeight} = \textit{rightHeight}の場合左高さ=rightHeight の場合、サブツリーの高さはleftHeight + 1 \textit{leftHeight} + 1左高さ+1、lca lcalca 就是 n o d e node ノード。 _ _ _ 矛盾による証明: iflca lcal c aは左側のサブツリーにあり、次にlca lcal c aは右のサブツリーの最も深い葉ノードの祖先ではありませんが、これは間違っています。lcalcal c aは右側のサブツリーにあり、次にlca lcal c a は、左のサブツリーの最も深い葉ノードの祖先ではありませんが、これも間違っています; iflca lcalca 在 n o d e node ノードeは、「最近」の要件を満たしていません。それで、lca lcal c a はノードノードのみになれますいいえ。 _ _ _
class Solution {
pair<int, TreeNode*> dfs(TreeNode *node) {
if (node == nullptr)
return {
0, nullptr};
auto [left_height, left_lca] = dfs(node->left);
auto [right_height, right_lca] = dfs(node->right);
if (left_height > right_height) // 左子树更高
return {
left_height + 1, left_lca};
if (left_height < right_height) // 右子树更高
return {
right_height + 1, right_lca};
return {
left_height + 1, node}; // 一样高
}
public:
TreeNode *subtreeWithAllDeepest(TreeNode *root) {
return dfs(root).second;
}
};
複雑さの分析:
- 時間計算量: O ( n ) \mathcal{O}(n)O ( n )。各ノードは 1 回だけ訪問されます。
- 空間計算量: O ( n ) \mathcal{O}(n)O ( n )。最悪の場合、二分木は連鎖であり、再帰にはO ( n ) \mathcal{O}(n)O ( n )スタックスペース。
より簡潔に書くと次のようになります。
class Solution {
public:
int depth[1010];
TreeNode* subtreeWithAllDeepest(TreeNode* root) {
if (root == nullptr) return nullptr;
TreeNode* left = root->left, *right = root->right;
TreeNode* lcaLeft = subtreeWithAllDeepest(root->left), *lcaRight = subtreeWithAllDeepest(root->right);
int dl = left ? depth[left->val] : 0, dr = right ? depth[right->val] : 0;
depth[root->val] = max(dl, dr) + 1;
if (dl > dr) return lcaLeft;
if (dr > dl) return lcaRight;
return root;
}
};