Problem with understanding the code of 968. Binary Tree Cameras - python

I am studying algorithms and trying to solve the LeetCode problem 968. Binary Tree Cameras:
You are given the root of a binary tree. We install cameras on the tree nodes where each camera at a node can monitor its parent, itself, and its immediate children.
Return the minimum number of cameras needed to monitor all nodes of the tree.
I got stuck on it, and after checking the discussion I better understood the logic, but I am still struggling to understand the code:
def minCameraCover(self, root):
self.res = 0
def dfs(root):
if not root: return 2
l, r = dfs(root.left), dfs(root.right)
if l == 0 or r == 0:
self.res += 1
return 1
return 2 if l == 1 or r == 1 else 0
return (dfs(root) == 0) + self.res
I don't understand why l, r == 0, 0 in a DFS function while the base case is set as if not root: return 2
What are the mechanics behind this that makes dfs(root.left), def(root.right) return 0?
So far I understood that a node has three states:
0: it's a leaf
1: it has a camera and the node is parent of a leaf
2: it's being covered, but does not have a camera on it.

The base case is set for a None, i.e. the absence of a node. Such a virtual position is never a problem, so we can count it as "covered", but there is no camera there. This is why the base case returns 2.
Now when a leaf node is encountered, then obviously both recursive calls will get None as argument and return 2.
Then the expression 2 if l == 1 or r == 1 else 0 will evaluate to 0, as neither l nor r are 1 (they are 2): theelse clause kicks in.
I hope this clarifies that for leaf nodes the return value will be 0, but also for other nodes this can happen: every time both recursive calls return 2, the caller will itself return 0.
Therefore the better explanation of the three states is:
1: the node has a camera
2: the node has no camera, but it is covered from below
0: the node has no camera yet and is not covered from below. If it has a parent, that parent should get a camera so to cover for this node. It it is the root, it must get a camera itself.

Related

Count number of leaves in a tree - Failed edge case?

In an online assessment I was asked to count the number of leaves in a tree. The tree is given in parent-array representation, meaning the tree has n nodes with labels 0, 1, 2, .., n-1, and you are passed a length n array p, where p[i] returns the label of the parent of node i, except when i is the root of the tree in which case p[i] is -1.
I guess one thing to note is that the problem was as stated above, so there were no extra conditions such as e.g. it being a binary tree.
I thought this was a fairly straight forward problem, but the code that I submitted failed a "Small Tree Case" on the testing platform (which does not let you see the test cases). It passed the other tests, including a performance test on a large tree. I've thought about it for a while but I still can not see what the flaw in my algorithm or handling of some edge case is. I guess one thing to note is that the problem was as stated above, so there were no extra conditions such as e.g. it being a binary tree.
def countLeaves(p):
n = len(p)
if p is None or n == 0 : return 0
if n == 1 or n == 2 : return 1
leaves = set(range(n))
for i in range(n):
if p[i] == -1: # i is root of tree with >1 node, can't be a leaf
leaves.discard(i)
else: # p[i] is parent of node i, can't be a leaf
leaves.discard(p[i])
return len(leaves)
In trying to fix the failed "Small tree case" I also tried returning None if p is None, returning None if n == 0, or both modifications together, but to no success. If anyone could point out what the error in my code may have been I would greatly appreciate it. Thank you.
I would try this:
def countLeaves(p):
n = len(p)
if p is None or n < 2 : return 0
leaves = set(range(n))
for i in range(n):
if p[i] == -1: # i is root of tree with >1 node, can't be a leaf
leaves.discard(i)
else: # p[i] is parent of node i, can't be a leaf
leaves.discard(p[i])
return len(leaves)
The only real change is that it considers trees with a single node to have no leaves.
According to Wolfram Mathworld:
A leaf of an unrooted tree is a node of vertex degree 1. Note that for a rooted or planted tree, the root vertex is generally not considered a leaf node, whereas all other nodes of degree 1 are.

Calculate height of an arbitrary (non-binary) tree

I'm currently taking on online data structures course and this is one of the homework assignments; please guide me towards the answer rather than giving me the answer.
The prompt is as follows:
Task. You are given a description of a rooted tree. Your task is to compute and output its height. Recall that the height of a (rooted) tree is the maximum depth of a node, or the maximum distance from a leaf to the root. You are given an arbitrary tree, not necessarily a binary tree.
Input Format. The first line contains the number of nodes n. The second line contains integer numbers from −1 to n−1 parents of nodes. If the i-th one of them (0 ≤ i ≤ n−1) is −1, node i is the root, otherwise it’s 0-based index of the parent of i-th node. It is guaranteed that there is exactly one root. It is guaranteed that the input represents a tree.
Constraints. 1 ≤ n ≤ 105.
My current solution works, but is very slow when n > 102. Here is my code:
# python3
import sys
import threading
# In Python, the default limit on recursion depth is rather low,
# so raise it here for this problem. Note that to take advantage
# of bigger stack, we have to launch the computation in a new thread.
sys.setrecursionlimit(10**7) # max depth of recursion
threading.stack_size(2**27) # new thread will get stack of such size
threading.Thread(target=main).start()
# returns all indices of item in seq
def listOfDupes(seq, item):
start = -1
locs = []
while True:
try:
loc = seq.index(item, start+1)
except:
break
else:
locs.append(loc)
start = loc
return locs
def compute_height(node, parents):
if node not in parents:
return 1
else:
return 1 + max(compute_height(i, parents) for i in listOfDupes(parents, node))
def main():
n = int(input())
parents = list(map(int, input().split()))
print(compute_height(parents.index(-1), parents))
Example input:
>>> 5
>>> 4 -1 4 1 1
This will yield a solution of 3, because the root is 1, 3 and 4 branch off of 1, then 0 and 2 branch off of 4 which gives this tree a height of 3.
How can I improve this code to get it under the time benchmark of 3 seconds? Also, would this have been easier in another language?
Python will be fine as long as you get the algorithm right. Since you're only looking for guidance, consider:
1) We know the depth of a node iif the depth of its parent is known; and
2) We're not interested in the tree's structure, so we can throw irrelevant information away.
The root node pointer has the value -1. Suppose that we replaced its children's pointers to the root node with the value -2, their children's pointers with -3, and so forth. The greatest absolute value of these is the height of the tree.
If we traverse the tree from an arbitrary node N(0) we can stop as soon as we encounter a negative value at node N(k), at which point we can replace each node with the value of its parent, less one. I.e, N(k-1) = N(k) -1, N(k-2)=N(k-1) - 1... N(0) = N(1) -1. As more and more pointers are replaced by their depth, each traversal is more likely to terminate by encountering a node whose depth is already known. In fact, this algorithm takes basically linear time.
So: load your data into an array, start with the first element and traverse the pointers until you encounter a negative value. Build another array of the nodes traversed as you go. When you encounter a negative value, use the second array to replace the original values in the first array with their depth. Do the same with the second element and so forth. Keep track of the greatest depth you've encountered: that's your answer.
The structure of this question looks like it would be better solved bottom up rather than top down. Your top-down approach spends time seeking, which is unnecessary, e.g.:
def height(tree):
for n in tree:
l = 1
while n != -1:
l += 1
n = tree[n]
yield l
In []:
tree = '4 -1 4 1 1'
max(height(list(map(int, tree.split()))))
Out[]:
3
Or if you don't like a generator:
def height(tree):
d = [1]*len(tree)
for i, n in enumerate(tree):
while n != -1:
d[i] += 1
n = tree[n]
return max(d)
In []:
tree = '4 -1 4 1 1'
height(list(map(int, tree.split())))
Out[]:
3
The above is brute force as it doesn't take advantage of reusing parts of the tree you've already visited, it shouldn't be too hard to add that.
Your algorithm spends a lot of time searching the input for the locations of numbers. If you just iterate over the input once, you can record the locations of each number as you come across them, so you don't have to keep searching over and over later. Consider what data structure would be effective for recording this information.

Runtime of two separate getHeight algorithms for a binary search tree

I have two separate implementations of a getHeight() method for a binary search tree (not necessarily a balanced BST though), one is iterative and one is recursive. Here's the iterative one:
def height(root): #iterative approach, uses a stack for a depth first search
max_height = 0
myStack = [root]
currentNode = None
root.level = True
while len(myStack) != 0:
currentNode = myStack[-1]
if currentNode.left is not None and currentNode.left.level is not True:
myStack.append(currentNode.left)
currentNode.left.level = True
continue
if currentNode.right is not None and currentNode.right.level is not True:
myStack.append(currentNode.right)
currentNode.right.level = True
continue
elif (currentNode.left is None or currentNode.left.level is True) and (currentNode.right is None or currentNode.right.level is True):
height = len(myStack) - 1
if height > max_height:
max_height = height
myStack.pop()
return max_height
and here's the recursive approach:
def recurseHeight(root):
add = 0
l, r = 0, 0
if root.left is not None:
l = 1 + recurseHeight(root.left)
if root.right is not None:
r = 1 + recurseHeight(root.right)
return l if l > r else r
So, I know from a space-complexity perspective, the recursive algorithm is better. However, from my understanding, the runtime of the iterative algorithm is O(n) (because it has to search all n nodes, and won't stop until that point), but I was wondering what the runtime of the recursive algorithm is. I know I would have to use the master theorem, and part of me thinks it would also be O(n) simply because I would have to visit all the nodes no matter what, but I'm not sure. Could anyone help find the runtime of the recursive algorithm?
(side note, I'm doing this to practice for interviews - if anyone has good problems/learning resources, don't hesitate to say them loud and proud :) )
As you are saying exactly it's O(n) as you always visit every node in the tree and visit each one only one time which means you need O(n) of work for the recursive version

Too slow queries in interval tree

I have a list of intervals and I need to return the ones that overlap with an interval passed in a query. What is special is that in a typical query around a third or even half of the intervals will overlap with the one given in the query. Also, the ratio of the shortest interval to the longest is not more than 1:5. I implemented my own interval tree (augmented red-black tree) - I did not want to use existing implementations because I needed support for closed intervals and some special features. I tested the query speed with 6000 queries in a tree with 6000 intervals (so n=6000 and m=3000 (app.)). It turned out that brute force is just as good as using the tree:
Computation time - loop: 125.220461 s
Tree setup: 0.05064 s
Tree Queries: 123.167337 s
Let me use asymptotic analysis. n: number of queries; n: number of intervals; app. n/2: number of intervals returned in a query:
time complexity brute force: n*n
time complexity tree: n*(log(n)+n/2) --> 1/2 nn + nlog(n) --> n*n
So the result is saying that the two should be roughly the same for a large n. Still one would somehow expect the tree to be noticeably faster given the constant 1/2 in front of n*n. So there are three possible reasons I can imagine for the results I got:
a) My implementation is wrong. (Should I be using BFS like below?)
b) My implementation is right, but I made things cumbersome for Python so it needs more time to deal with the tree than to deal with brute force.
c) everything is OK - it is just how things should behave for a large n
My query function looks like this:
from collections import deque
def query(self,low,high):
result = []
q = deque([self.root]) # this is the root node in the tree
append_result = result.append
append_q = q.append
pop_left = q.popleft
while q:
node = pop_left() # look at the next node
if node.overlap(low,high): # some overlap?
append_result(node.interval)
if node.low != None and low <= node.get_low_max(): # en-q left node
append_q(node.low)
if node.high != None and node.get_high_min() <= high: # en-q right node
append_q(node.high)
I build the tree like this:
def build(self, intervals):
"""
Function which is recursively called to build the tree.
"""
if intervals is None:
return None
if len(intervals) > 2: # intervals is always sorted in increasing order
mid = len(intervals)//2
# split intervals into three parts:
# central element (median)
center = intervals[mid]
# left half (<= median)
new_low = intervals[:mid]
#right half (>= median)
new_high = intervals[mid+1:]
#compute max on the lower side (left):
max_low = max([n.get_high() for n in new_low])
#store min on the higher side (right):
min_high = new_high[0].get_low()
elif len(intervals) == 2:
center = intervals[1]
new_low = [intervals[0]]
new_high = None
max_low = intervals[0].get_high()
min_high = None
elif len(intervals) == 1:
center = intervals[0]
new_low = None
new_high = None
max_low = None
min_high = None
else:
raise Exception('The tree is not behaving as it should...')
return(Node(center, self.build(new_low),self.build(new_high),
max_low, min_high))
EDIT:
A node is represented like this:
class Node:
def __init__(self, interval, low, high, max_low, min_high):
self.interval = interval # pointer to corresponding interval object
self.low = low # pointer to node containing intervals to the left
self.high = high # pointer to node containing intervals to the right
self.max_low = max_low # maxiumum value on the left side
self.min_high = min_high # minimum value on the right side
All the nodes in a subtree can be obtained like this:
def subtree(current):
node_list = []
if current.low != None:
node_list += subtree(current.low)
node_list += [current]
if current.high != None:
node_list += subtree(current.high)
return node_list
p.s. note that by exploiting that there is so much overlap and that all intervals have comparable lenghts, I managed to implement a simple method based on sorting and bisection that completed in 80 s, but I would say this is over-fitting... Amusingly, by using asymptotic analysis, I found it should have app. the same runtime as using the tree...
If I correctly understand your problem, you are trying to speed up your process.
If it is that, try to create a real tree instead of manipulating lists.
Something that looks like :
class IntervalTreeNode():
def __init__(self, parent, min, max):
self.value = (min,max)
self.parent = parent
self.leftBranch = None
self.rightBranch= None
def insert(self, interval):
...
def asList(self):
""" return the list that is this node and all the subtree nodes """
left=[]
if (self.leftBranch != None):
left = self.leftBranch.asList()
right=[]
if (self.rightBranch != None):
left = self.rightBranch.asList()
return [self.value] + left + right
And then at start create an internalTreeNode and insert all yours intervals in.
This way, if you really need a list you can build a list each time you need a result and not each time you make a step in your recursive iteration using [x:] or [:x] as list manipulation is a costly operation in python. It is possible to work also using directly the nodes instead of a list that will greatly speed up the process as you just have to return a reference to the node instead of doing some list addition.

Recursive To Iteratative - AVL Tree - isBalanced

I have to write an iterative algorithm to determine whether an AVL-Tree is balanced or not.
My first approach was to find a direct way, but after hours I gave up, so I wrote the algorithm recursive and tried to convert it.
here's the source code of the recursive-version (written in python)
def isBalanced(root):
if root == None:
return -1
lh = isBalanced(root.left)
rh = isBalanced(root.right)
if lh == -2 or rh == -2:
return -2
if abs(lh - rh) > 1:
return -2
return max(lh, rh) + 1
My problem is now, that I'm not able to convert it, maybe one of you could give me a hint or solve my problem
thanks in advance
Remember that recursive programs use call stacks. You can transform any recursive program to an iterative one by using a stack. In the following code, I use two.
def isBalanced(root):
nodes = [root]
results = []
while nodes:
node = nodes.pop()
if node is None:
results.append(-1)
else if node == 0: # 0 is a flag we use for when we are ready to combine results
lh = results.pop()
rh = results.pop()
if abs(lh - rh) > 1:
return -2 # we could have continued with the stack; this is just a shortcut
else:
results.append(max(lh, rh) + 1)
else:
nodes.push(0)
nodes.push(node.left)
nodes.push(node.right)
return results, # results will have only one value
Here, stack is a stack of nodes to check, and the results of those nodes.

Categories