模擬インタビューの1つで問題が発生しました。特定のノードの1つが既に起動してから、バイナリツリーが完全に燃え尽きるまでの時間を見つける必要がありました。
"リーフツリーからバイナリツリーが燃え始めます。ツリー全体が燃えるまでの時間(ノードからノードへの焼き付けに1秒)はどのくらいですか?火はノードからすべてのパスに広がります。 "
次のようなツリーがあるとします。Nは起動中のノードです。これは、秒がsである最初の秒で発生するため、0番目の秒で発生します。
1
/ \
1 1
/ \ \
1 1 1
/ \ \
1 N 1
\
1
1秒が経過すると、ツリーはより多くの書き込みノードで更新されます。次の秒(s + 1)の例は次のようになります。
1
/ \
1 1
/ \ \
1 N 1
/ \ \
1 N 1
\
1
次の秒(s + 2)の例は次のようになります。
1
/ \
N 1
/ \ \
1 N 1
/ \ \
N N 1
\
1
これで、3秒目(s + 3)は次のようになります。
N
/ \
N 1
/ \ \
N N 1
/ \ \
N N 1
\
1
同じパターンで、ツリーは(s + 7)で焼き付けられます
N
/ \
N N
/ \ \
N N N
/ \ \
N N N
\
N
少し理解した後、私はそれを行う方法を見つけるために小さな研究をしました。このクールな article を見つけて、それをフォローして、背後にあるアイデアを実装しました。
私のアプローチは、最も遠いノードからノードを探すために、ツリーの高さとともに直径を見つけることでした。ただし、関数を実装すると、開始ノードから指定ノードの終わりまでの結果しか得られません前の親ノードをチェックせずに。 Python 3:
# Tree class
class Node:
def __init__(self, key):
self.left = None
self.right = None
self.value = key
# Maximum height of a tree
def maxHeight(root):
if root is None:
return 0
else:
return 1 + max(maxHeight(root.left), maxHeight(root.right))
# Diameter of the tree
def maxDiameter(root):
how_long = 0
if root is None:
return 0
else:
root_diameter = maxHeight(root.left) + maxHeight(root.right)
left_diameter = maxDiameter(root.left)
right_diameter = maxDiameter(root.right)
how_long = max(max(left_diameter, right_diameter), root_diameter)
return how_long
# Sample code
root = Node(1)
root.left = Node(1)
root.right = Node(1)
root.left.left = Node(1)
root.left.right = Node(1)
root.left.right.left = Node(1)
root.left.right.right = Node(1)
root.right.right = Node(1)
root.right.right.right = Node(1)
root.right.right.right.right = Node(1)
print ("Starting from the given node, it will take %ds to burn the whole tree" % (maxDiameter(root.left.right)))
この例で予想される出力は6秒です(指定されたノードの0から開始)。しかし、再び、私はツリーの全範囲を取得していません。私自身の理解では、すべてのケースで動作する必要があります。そこで、ここで役立つ検索は、DFSまたはBFSでしょうか?これを念頭に置くことで、解決策に導くことができると思いますが、繰り返します。フィードバックは大歓迎です:)
この投稿で何が起こったのか疑問に思う人のために、使用されたソリューションはこれでした:
LeafSide = []
class Node:
"""Tree class."""
def __init__(self, key):
"""Declare values of a node."""
self.left = None
self.right = None
self.value = key
def leafHeight(root, leaf):
"""Height of the leaf."""
if root is None:
return 0
else:
if root.left is leaf:
aux = 1 + leafHeight(root.right, leaf)
LeafSide.append(aux)
return 1
if root.right is leaf:
aux = 1 + leafHeight(root.left, leaf)
LeafSide.append(aux)
return 1
return 1 + max(leafHeight(root.left, leaf), leafHeight(root.right, leaf))
def timeBurn(root, leaf):
"""How long will it take to burn the the node to furthest node."""
hl = leafHeight(root.left, leaf)
hr = leafHeight(root.right, leaf)
opposite_LeafSide = 1 + hl + hr
return max(opposite_LeafSide, LeafSide[0])
if __name__ == '__main__':
root = Node(1)
root.left = Node(1)
root.right = Node(1)
root.left.left = Node(1)
root.left.right = Node(1)
root.left.right.left = Node(1)
root.left.right.right = Node(1)
root.right.right = Node(1)
root.right.right.right = Node(1)
root.right.right.right.right = Node(1)
print ("Starting from the given node, it will take %ds to burn the whole tree" % (timeBurn(root, root.left.right)))
時間:O(n)
スペース:O(n)
気付いた場合、各ノードの値は1です。ノードの値はこの問題には関係ありません。それはその中のある値を表しているだけです。私が1つ持っている理由は、2番目(1秒のノード)を考えるためです。私を助けてくれたみんなに感謝します。私はあなたたちが話していたコメントとアプローチのすべてを読んで楽しんだ:)。コードの改善方法についてより良いアイデアをお持ちの場合は、以下にコメントしてください。
あなたには次のものが必要だと思います。
dStart
と呼びます)。dSameSide
と呼びますdCommonAncestor
と呼びます)dOppositeSide
。この情報はすべて、ツリーの単一の順序走査から取得できます。
開始ノードからツリーのその側の最も深いノードまでのステップ数は(dSameSide - dCommonAncestor) + (dStart - dCommonAncestor)
。
開始ノードから反対側の最も深いノードまでのステップ数は(dStart + dOppositeSide)
。
そして、ツリー全体を焼くために必要なステップの数は、これらの最大数です。
実装はあなたにお任せします。おそらく バイナリツリーの2つのノードの最も低い共通の祖先を見つける方法 役立つでしょう。
これは、現在のノードから開始ノードまでのパスの長さを返す再帰関数を使用して解決できます(開始ノードがその下にない場合は、リーフへの最長パスのみ)。
開始ノードからこれまでの最長パスを返すようにすることもできます。開始パスが見つかった場合、これは左右の子(現在のノードに対して1つ)で呼び出された関数の合計です。
これは、 m69 で説明されているソリューションに似ています。
これは、O(n)時間で実行されます。関数は一定の時間で実行されるため(再帰呼び出しを除外する場合)、関数はノードごとに最大3回呼び出されます。そして、その左と右の子については、葉ノードの場合)。
これはO(height) spaceを使用します。変数を使用して関数呼び出し以外に何も保存せず、任意の時点でメモリに保持できる最大数は再帰の深さに等しい(つまり、木の高さ)。
class Node:
def __init__(self, key):
self.left = None
self.right = None
self.value = key
# returns a Tuple (max = the longest path so far, dist = current path)
def _recurse(node, start):
if node is None:
return (None, 0)
else:
max_left, dist_left = _recurse(node.left, start)
max_right, dist_right = _recurse(node.right, start)
# this node is the starting node
if node == start:
return (0, 0)
# the starting node is in left or right
Elif max_right is not None or max_left is not None:
return (dist_right + dist_left + 1,
(dist_left if max_right is None else dist_right) + 1)
# we haven't seen the starting node
else:
return (None, max(dist_left, dist_right) + 1)
def time_to_burn(root, start):
return _recurse(root, start)[0]
テスト:
root = Node(1)
root.left = Node(1)
root.right = Node(1)
root.left.left = Node(1)
root.left.right = Node(1)
root.left.right.left = Node(1)
root.left.right.right = Node(1)
root.right.right = Node(1)
root.right.right.right = Node(1)
root.right.right.right.right = Node(1)
>>> time_to_burn(root, root.left.right.right)
7
基本的な考え方は、各ノードに3つの戻り値を持たせることです。
max
。これは、これまでに取得した開始ノードからの最長パスです(開始ノードがまだ表示されていない場合はNone
)。above
、これは開始ノードの上のノードの数です(開始ノードがまだ表示されていない場合はNone
)。below
。これは、開始ノードの下の最長パスです(開始ノードがまだ表示されていない場合は、現在のノードからの最長パスです)。子サブツリーからabove
およびbelow
を計算するのはかなり簡単です-詳細についてはコードを参照してください。
現在のノードからの最長パスmax
を最大値として定義できます。
below
)コード:(_recurse
上記の関数)
# returns a Tuple (max, above, below)
def _recurse(node, start):
if node is None:
return (None, None, 0)
else:
max_left, above_left, below_left = _recurse(node.left, start)
max_right, above_right, below_right = _recurse(node.right, start)
# this node is the starting node
if node == start:
below = max(below_left, below_right)
return (below, 0, below)
# the starting node is in left or right
Elif above_right is not None or above_left is not None:
return (max((0 if above_right is None else above_right) + below_left,
(0 if above_left is None else above_left) + below_right) + 1,
(above_right if above_left is None else above_left) + 1,
below_right if above_left is None else below_left)
# we haven't seen the starting node
else:
return (None, None, max(below_left, below_right) + 1)
>>> time_to_burn(root, root.left.right)
6
以下の例をご覧ください。最初に、火の根から葉まで移動します(F):
N
/ \
N N
/ \ \
N N N
/ \ \
N F N
/ \ \
N N N
\
N
次に、親ノードまで移動し、燃える葉までの距離(1)と左のサブツリーの高さ(3)の合計4を取得します。
N
/ \
N N
/ \ \
N 4 N
/ \ \
3 1 N
/ \ \
N 2 N
\
1
したがって、4が現在の最大値です。次に、親ノードまで移動し、燃える葉までの距離(2)と左のサブツリーの深さ(1)の合計、つまり3を取得します。
N
/ \
3 N
/ \ \
1 2 N
/ \ \
N 1 N
/ \ \
N N N
\
N
したがって、現在の最大値は4のままです。親ノードまで移動し、燃える葉までの距離(3)と右側のサブツリーの深さ(4)の合計7を取得します。
7
/ \
3 4
/ \ \
N 2 3
/ \ \
N 1 2
/ \ \
N N 1
\
N
新しい最大値は7で、ルートノードに到達したので、7秒が答えです。x秒後に起動しているノードを確認することで確認できます。
3
/ \
2 4
/ \ \
3 1 5
/ \ \
2 0 6
/ \ \
3 3 7
\
4
ルートが最長パスの一部ではない例を次に示します。
N N 3 2
/ \ / \ / \ / \
N N 4 N 2 1 1 3
/ \ / \ / \ / \
N F 3 1 N 1 2 0
/ / / /
N 2 N 3
/ / / /
N 1 N 4
遭遇した最大値は、燃えている葉の親で4でした。
これは簡単なJavaScriptコードスニペットです(Pythonは話せませんが、これは擬似コードとして機能するはずです)。私の答えからの最初の例のツリーのハードコーディングされたバージョンを使用します。ご覧のとおり、ツリーの深さ優先走査を1回行います。
function burn(root) {
var maximum = 0;
traverse(root);
return maximum;
function traverse(node) {
if (node.onfire) {
return {steps: 1, onfire: true};
}
var l = node.left ? traverse(node.left) : {steps: 0};
var r = node.right ? traverse(node.right) : {steps: 0};
if (l.onfire || r.onfire) {
maximum = Math.max(maximum, l.steps + r.steps);
return {steps: (l.onfire ? l.steps : r.steps) + 1, onfire: true};
}
return {steps: Math.max(l.steps, r.steps) + 1};
}
}
var tree = {left: {left: {left: null, right: null}, right: {left: {left: {left: null, right: null}, right: {left: null, right: {left: null, right: null}}}, right: {left: null, right: null, onfire:true}}}, right: {left: null, right: {left: null, right: {left: null, right: {left: null, right: null}}}}}
document.write(burn(tree));
BFSを使用すると迅速に実行できます。
class Node:
def __init__(self, value):
self.left = None
self.right = None
self.parent = None
self.value = value
def set_left(self, other):
self.left = other
other.parent = self
def set_right(self, other):
self.right = other
other.parent = self
def get_distance_to_furthest(node):
visited = set()
queue = [(node, 0)]
max_d = 0
while queue:
node, d = queue.pop(0)
if node in visited:
continue
visited.add(node)
max_d = max(d, max_d)
if node.left:
queue.append((node.left, d + 1))
if node.right:
queue.append((node.right, d + 1))
if node.parent:
queue.append((node.parent, d + 1))
return max_d
# Sample code
root = Node(1)
root.set_left(Node(1))
root.set_right(Node(1))
root.left.set_left(Node(1))
root.left.set_right(Node(1))
root.left.right.set_left(Node(1))
root.left.right.set_right(Node(1))
root.right.set_right(Node(1))
root.right.right.set_right(Node(1))
root.right.right.right.set_right(Node(1))
print(
"Starting from the given node, it will take %ds to burn the whole tree"
% (get_distance_to_furthest(root.left.right))
)
バイナリツリーは特別な種類のグラフであるため、すべてのノードを調べて、各ノードから火災が始まったノードまでの距離を追跡できます。結果は、あなたが見た中で最も高い距離です。
以下は、ソースノード(リーブノードまたは非リーブノード)を指定して、ツリーの書き込みにかかる時間を見つけるためのソリューションの1つです。
解決するアプローチは次のとおりです。
1)ツリーでソースノードを見つけ、ノードの高さを見つけます(ここでは変数 "sourceDepth"に格納しています)
2)指定されたソースノードのすべての祖先
->Take distance from the source node and present node
->Find the height of the opposite subtree in which the source is not present
->Add both of the above + 1 (for the Edge between ancestor and sub tree).Lets call this d
3)ステップ2のすべてのdの最大値とステップ1のsourceDepthを取得します。これは必須の回答です。
以下の例では、sourceを3にします。
7
/ \
8 4
/ \ \
10 9 3
/ \ \
0 11 2
\
1
ソースの深さ(3)は、sourceDepth = 2
ソースのすべての祖先は[7、4]です
祖先4の場合:
ソースからの距離は1で、ソースの反対方向にサブツリーはありません(つまり、ソースは右サブツリーにあり、左サブツリーはありません)。 dは1です。
先祖のために7
ソースからの距離は2で、ソースの反対方向のサブツリーの高さは2です。したがって、ここのdは2 + 2 + 1 = 5です。 (1は7〜8のエッジ用です)
ノード7の高さ= 2の右サブツリー
8
/ \
10 9
/ \
0 11
この場合の解決策は、最大の(2,1,5)5です。答えは5です
上記のソリューションのJava実装は次のとおりです。
static int max = Integer.MIN_VALUE;
private static int find(TreeNode<Integer> root, int source, int sourceDepth) {
if (root == null) {
return -1;
}
if (root.getData() == source) {
sourceDepth = getDepth(root);
return 0;
}
int left = find(root.getLeft(), source, sourceDepth);
if (left != -1) {
int rightDepth = getDepth(root.getRight()) + 1;
max = Math.max(rightDepth + left + 1, sourceDepth);
return left + 1;
}
int right = find(root.getRight(), source, sourceDepth);
if (right != -1) {
int leftDepth = getDepth(root.getRight()) + 1;
max = Math.max(leftDepth + right + 1, sourceDepth);
return right + 1;
}
return -1;
}
private static int getDepth(TreeNode<Integer> root) {
if (root == null) {
return -1;
}
return Math.max(getDepth(root.getLeft()), getDepth(root.getRight())) + 1;
}
ここで、ソースは、ここで要求された必須の回答を提供する任意のLeaveノードにすることができます。